I Describing my experience using ABAP2XLSX to facilitate building an Excel spreadsheet from the content used to present multiple ALV reports and then send that spreadsheet to an internet email recipient. The example program I presented was written using the procedural programming model – that is, despite its use of classes from both ABAP2XLSX and the Business Communication Service, it did not require the creation of any new local or global object-oriented components. I thought this program might make a good candidate to illustrate how to change an ABAP program to transition it from a procedural design to an object-oriented design using local classes, transforming the code in a series of steps rather than all of it in one big gulp, with explanations to accompany the concepts associated with each change. Those who do not have ABAP2XLSX available at their site will still be able to follow along since I have adapted the example program to account for its absence.
To recap from the preceding blog, the ABAP program presents 3 ALV reports, one after the other using the records of tables SFLIGHT, SCARR and SBOOK, respectively, converting the content of each report into a separate worksheet of an Excel spreadsheet, and then sends the spreadsheet to our own internet email address. Here is the original procedural source code as we left it in the previous blog:
report.
types : row_counter type n length 02.
types : email_recipient
type adr6-smtp_addr.
data : excel type ref to zcl_excel ##NEEDED.
parameters : rowcount type row_counter.
parameters : recipien type email_recipient.
initialization.
select single smtp_addr
into recipien
from adr6 ##WARN_OK
inner join
usr21 on usr21~persnumber eq adr6~persnumber
where usr21~bname eq sy-uname.
start-of-selection.
perform display_flight_rows using rowcount.
perform display_carrier_rows using rowcount.
perform display_booking_rows using rowcount.
perform send_excel_via_email using recipien.
form display_flight_rows using row_count
type row_counter
raising zcx_excel.
data : flight_stack type standard table of sflight
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = flight_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table flight_stack
from sflight
up to row_count rows.
alv_report->display( ).
perform copy_table_to_excel_worksheet using flight_stack 'Flights'.
endform.
form display_carrier_rows using row_count
type row_counter
raising zcx_excel.
data : carrier_stack type standard table of scarr
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = carrier_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table carrier_stack
from scarr
up to row_count rows.
alv_report->display( ).
perform copy_table_to_excel_worksheet using carrier_stack 'Carriers'.
endform.
form display_booking_rows using row_count
type row_counter
raising zcx_excel.
data : booking_stack type standard table of sbook
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = booking_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table booking_stack
from sbook
up to row_count rows.
alv_report->display( ).
perform copy_table_to_excel_worksheet using booking_stack 'Bookings'.
endform.
form copy_table_to_excel_worksheet using source_stack
type standard table
source_description
type string
raising zcx_excel.
constants : first_column type char1 value 'A'
.
data : worksheet type ref to zcl_excel_worksheet
, worksheet_title
type zexcel_sheet_title
, table_settings type zexcel_s_table_settings
.
table_settings-table_style = zcl_excel_table=>builtinstyle_medium2.
table_settings-show_row_stripes
= abap_true.
table_settings-nofilters = abap_true.
table_settings-top_left_column
= first_column.
table_settings-top_left_row = 01.
if excel is not bound.
create object excel.
worksheet = excel->get_active_worksheet( ).
else.
worksheet = excel->add_new_worksheet( ).
endif.
worksheet_title = source_description.
worksheet->set_title( worksheet_title ).
worksheet->bind_table(
ip_table = source_stack
is_table_settings = table_settings
).
endform.
form send_excel_via_email using recipient type email_recipient.
constants : excel_file_type
type string value '.xlsx'
, file_name_parameter
type string value '&SO_FILENAME='
.
data : excel_writer type ref to zif_excel_writer
, excel_as_xstring
type xstring
, excel_as_xstring_bytecount
type i
, excel_as_solix_stack
type solix_tab
, mail_send_request
type ref to cl_bcs
, mail_message type ref to cl_document_bcs
, any_bcs_exception
type ref to cx_bcs
, diagnostic type string
, mail_title type so_obj_des
, mail_text_stack
type soli_tab
, mail_text_entry
like line
of mail_text_stack
, mail_attachment_subject
type sood-objdes
, mail_attachment_bytecount
type sood-objlen
, mail_attachment_header_stack
type soli_tab
, mail_attachment_header_entry
like line of mail_attachment_header_stack
, internet_email_recipient
type ref to if_recipient_bcs
, successful_send
type abap_bool
, file_name type string
.
" Much of the code here was lifted from method send_mail of
" class lcl_ouput, defined in object ZDEMO_EXCEL_OUTPUTOPT_INCL:
concatenate sy-repid " this report name
sy-datum " current date
sy-uzeit " current time
excel_file_type " excel file extension
into file_name.
mail_title = file_name.
mail_attachment_subject = file_name.
mail_text_entry = 'See attachment'.
append mail_text_entry
to mail_text_stack.
concatenate file_name_parameter
file_name
into mail_attachment_header_entry.
append mail_attachment_header_entry
to mail_attachment_header_stack.
create object excel_writer type zcl_excel_writer_2007.
excel_as_xstring = excel_writer->write_file( excel ).
excel_as_solix_stack = cl_bcs_convert=>xstring_to_solix( iv_xstring = excel_as_xstring ).
excel_as_xstring_bytecount = xstrlen( excel_as_xstring ).
mail_attachment_bytecount = excel_as_xstring_bytecount.
try.
mail_message = cl_document_bcs=>create_document(
i_type = 'RAW' "#EC NOTEXT
i_text = mail_text_stack
i_subject = mail_title
).
mail_message->add_attachment(
i_attachment_type = 'XLS' "#EC NOTEXT
i_attachment_subject = mail_attachment_subject
i_attachment_size = mail_attachment_bytecount
i_att_content_hex = excel_as_solix_stack
i_attachment_header = mail_attachment_header_stack
).
mail_send_request = cl_bcs=>create_persistent( ).
mail_send_request->set_document( mail_message ).
internet_email_recipient = cl_cam_address_bcs=>create_internet_address( recipient ).
mail_send_request->add_recipient( internet_email_recipient ).
successful_send = mail_send_request->send( ).
commit work.
if successful_send eq abap_false.
message i500(sbcoms) with recipient.
else.
message s022(so).
message 'Document ready to be sent - Check SOST' type 'I'.
endif.
catch cx_bcs into any_bcs_exception.
diagnostic = any_bcs_exception->if_message~get_text( ).
message diagnostic type 'I'.
endtry.
endform.
Open your favorite ABAP editor, make a copy of this ABAP program and follow along as we apply changes to transform it from a procedural design into an object-oriented design using local classes. For those who do not have ABAP2XLSX available at their site, make the following changes to the source code:
◈ change the definition of field excel from:
data : excel type ref to zcl_excel ##NEEDED.
to
data : excel type string ##NEEDED.
◈ replace the two subroutines copy_table_to_excel_worksheet and send_excel_via_email appearing at the end of the program with the following code:
form copy_table_to_excel_worksheet using source_stack
type standard table
source_description
type string
raising zcx_excel.
data : source_stack_lines type string.
describe table source_stack lines source_stack_lines.
concatenate excel
source_stack_lines
source_description
into excel separated by space.
endform.
form send_excel_via_email using recipient type email_recipient.
data : message type string.
concatenate excel
'would be sent to'
recipient
into message separated by space.
message message type 'I'.
endform.
Getting started
First, we need to review what our program does and to identify the types of classes that would facilitate its processing. This program produces 3 ALV reports, presents each one to the user, one after the other, and then includes the report content in an Excel spreadsheet included as an attachment to an email sent across the internet. So, if we were to describe the entities that we would need to facilitate this we might find we have the following:
◈ flight_report
◈ carrier_report
◈ booking_report
◈ excel_spreadsheet_manager
One of the basic principles of OO design is known as Abstraction, and often it is explained in the context of a class being an abstraction to represent some real-world counterpart. The real-world counterparts of our program are the 3 reports and the Excel spreadsheet manager. Accordingly, we shall be defining 4 OO local classes with these names. Another of the basic principles of OO design is known as Encapsulation, and once we define these classes we will have each of them provide all the processing associated with the real-world entity it represents.
Let’s start with this and see how we might go about changing the code to achieve an OO design. I like to structure a report containing local OO classes such that the classes appear first, followed by any classic ABAP statements, such as parameters, select-options and event blocks. This promotes good component encapsulation since the classes cannot possibly refer to any data defining statements that could appear ahead of them (since there are none), making them easily separable from the report if we were to decide later to transform them into global classes.
First, we’ll define a local OO class to describe the flight_report. We’ll keep this simple for now and define it as a static class, meaning that to use it will not require that we instantiate the class. It should contain a single static method named produce_report whose signature is comparable to the signature defined for subroutine display_flight_rows. The skeleton of the class might look like this:
class flight_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type row_counter
raising
zcx_excel
.
endclass.
class flight_report implementation.
method produce_report.
endmethod.
endclass.
Place these lines immediately after the report statement. At this point the syntax check fails due to a reference to the unknown row_counter in the signature of the produce_report method. Indeed, this is defined by a types statement immediately following the final endclass statement defining this new flight_report class, so cut and paste this types statement ahead of the flight_report class.
The syntax check now passes again, but our new local class also now contains a reference to a global types statement defined in this program. If later we were to decide to convert local class flight_report into a global class, we’d need to resolve this reference to a component defined within this program. Let’s resolve this now by encapsulating this types statement in a local OO interface enabling us to exchange data between entities in this program, an interface we’ll call data_exchangeable. Do this by surrounding the types statement defining row_counter with the following two statements:
interface data_exchangeable.
endinterface.
This again results in a syntax error due to a reference to the unknown row_counter in the signature of the produce_report method, so prefix the reference to row_counter in this signature with its interface qualifier “data_exchangable=>”, as in:
importing
row_count
type data_exchangeable=>row_counter
At this point a syntax check will still fail due to all the other references throughout the program having a reference to a now-undefined type row_counter. Resolve this now for each of these locations similarly by prefixing the reference to row_counter with its interface qualifier. You should find this necessary at four more locations in the code before the syntax check finally passes.
Fine, we did all that, but why, you might be wondering, did we bother encapsulating the types statement for row_counter into a local interface? The answer is that if now we decide to convert local class flight_report into a global_class, its only reference to an entity defined in this program is an entity now contained in a local interface, so globalizing the flight_report class now would require that we also globalize the data_exchangeable interface it references.
Although the syntax check now passes, the new class flight_report has nothing to do because its method produce_report is empty, so now do the following:
◈ Move the code appearing between form display_flight_rows and its corresponding endform into method produce_report.
◈ Remove the corresponding form and endform statements.
This will cause a syntax check failure for the statement calling the now-missing subroutine, so replace statement
perform display_flight_rows using rowcount.
with statement
call method flight_report=>produce_report exporting row_count = rowcount.
Now a syntax check will pass again. Finally, we have succeeded in defining our first local class. Executing the program at this point should prove that it still works as before.
Defining a second local class
Follow the same process we used for creating class flight_report to transform the form-endform subroutine display_carrier_rows into its equivalent local class carrier_report, placing this class after class flight_report. Executing the program at this point should prove that it still works as before.
Defining a third local class
After that is completed, follow that same process again to transform the form-endform subroutine display_booking_rows into its equivalent local class booking_report, placing this class after class carrier_report. Executing the program at this point should prove that it still works as before.
Taking a moment for review
So far, we have taken a procedural program and transformed it by adding to it some local object-oriented classes and interfaces. Specifically, we’ve added three local static classes, each one following the same basic model as the other two, and one local interface. With this version we have three report classes representing their real-world counterparts, adhering to the object-oriented principle of Abstraction. In addition, we have inserted into each of these three report classes all the processing associated with producing its respective report, adhering to the object-oriented principle of Encapsulation.
At this point we still have two form-endform subroutines remaining in the program and we already have identified one other local class that we have yet to implement.
Defining a fourth local class
We made some progress but one of the things still glaringly apparent in this program is its use of a global variable defined in the data statement shown below:
types : email_recipient
type adr6-smtp_addr.
data : excel type ref to zcl_excel ##NEEDED.
parameters : rowcount type row_counter.
parameters : recipien type email_recipient.
Good object-oriented design would suggest that we should strive to eliminate the use of global variables except where they are absolutely necessary. A good example of a global variable that is absolutely necessary is any field defined to appear on a GUI screen, such as the parameter statements shown above. However, GUI screens cannot be defined as part of ABAP OO classes – other techniques are used instead. Meanwhile, the data field named excel is a good example of the definition of a global variable that should be avoided.
Create the skeleton for the new local class excel_spreadsheet_manager, for now placing it after local class booking_report. It should contain two static methods, each named the same as one of the two remaining form-endform subroutines (copy_table_to_excel_worksheet; send_excel_via_email), with each method having a signature comparable to the signature defined for its counterpart subroutine. Here is its code:
class excel_spreadsheet_manager definition
abstract
final.
public section.
class-methods: copy_table_to_excel_worksheet
importing
source_stack
type standard table
source_description
type string
raising
zcx_excel
, send_excel_via_email
importing
recipient
type email_recipient
.
endclass.
class excel_spreadsheet_manager implementation.
method copy_table_to_excel_worksheet.
endmethod.
method send_excel_via_email.
endmethod.
endclass.
At this point the syntax check fails due to email_recipient being undefined. To resolve this issue, let’s move the types statement for email_recipient to immediately follow the report statement. This causes a syntax check to pass again but notice that yet again we have a global types statement preceding the definitions of our local classes.
Since email_recipient is defined on a types statement and facilitates communication between various entities in this program, serving a similar purpose to the way row_count was defined, which we had moved into the data_exchangeable local interface, let’s also move the types definition for email_recipient to the end of the data_exchangeable local interface and similarly prefix all references to email_recipient with its interface qualifier “data_exchangable=>”. Afterward a syntax will pass.
As we saw when we did this with class flight_report, we have made changes so that the only reference by new class excel_spreadsheet_manager to an entity defined in this program is an entity now contained in a local interface, and if we were to decide to convert local class excel_spreadsheet_manager to a global class we also would need to convert local interface data_exchangeable to a global interface along with it.
Now move the code from their form-endform counterparts into the new methods for copy_table_to_excel_worksheet and send_excel_via_email, respectively, retaining the empty form-endform statements. Perform a syntax check at this point and you will see that the global field named excel is not available to method copy_table_to_excel_worksheet. Indeed, this global field is defined further on in the code, so we should expect this to be the case, but more to the point is that we want this field no longer to be defined as a global variable but defined as an attribute of class excel_spreadsheet_manager, so do the following:
◈ Define a private section for the new class excel_spreadsheet_manager.
◈ Move the global variable named excel into this private section and define it as a static attribute (class-data).
◈ Discard the ##NEEDED pragma from definition for attribute excel, no longer necessary since the field no longer is defined globally.
It should look like this:
private section.
class-data : excel type ref to zcl_excel.
endclass.
or, if ABAP2XLSX is not available at your site, like this:
private section.
class-data : excel type string.
endclass.
At this point the syntax check passes again, but we still have references to the empty form-endform routines, so let’s remove these empty subroutines and replace the calls to them with calls to the corresponding methods of local class excel_spreadsheet_manager, as in:
◈ Replace statement
perform copy_table_to_excel_worksheet using flight_stack 'Flights'.
with statement
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
◈ Replace statement
perform copy_table_to_excel_worksheet using carrier_stack 'Carriers'.
with statement
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = carrier_stack
source_description = 'Carriers'.
◈ Replace statement
perform copy_table_to_excel_worksheet using booking_stack 'Bookings'.
with statement
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = booking_stack
source_description = 'Bookings'.
◈ Replace statement
perform send_excel_via_email using recipien.
with statement
call method excel_spreadsheet_manager=>send_excel_via_email exporting recipient = recipien.
At this point a syntax check will fail for the first replacing call method statement shown above indicating:
Type “EXCEL_SPREADSHEET_MANAGER” is unknown
This is because the definition of the excel_spreadsheet_manager class follows the reference to it by this statement in the flight_report class. This is a consequence of the ABAP compiler being a single-pass complier, meaning that it resolves references as it encounters them while making only a single pass through the source code. Accordingly, any local data and class definitions must precede any references made to them. So here is a case where the local class excel_spreadsheet_manager needs to precede any classes using it. The solution is to relocate the excel_spreadsheet_manager class to appear ahead of any references to it, so cut and paste this local class immediately after the local interface.
After relocating this class the syntax check now passes again. Executing the program at this point should prove that it still works as before.
Notice that we’ve eliminated the global variable and very few lines remain outside of our local OO classes and interfaces – only the final 14 statements.
Defining a fifth local class
Since we have declared that our process is to present the 3 ALV reports and then send the content as a spreadsheet via email, we also shall define a local OO class to facilitate this processing, called process_driver. Accordingly, the next thing we’re going to do is to consolidate all the method calls in the start-of-selection event block into a single call to a new method defined in the new class process_driver. We’ll name the new method drive_process, whose signature should be capable of accepting a row count and an email recipient value corresponding to the values the user would provide in the two parameters appearing on the initial selection screen. First let’s define the class skeleton, placing it between the end of the booking_report class and the parameters statement defining rowcount:
class process_driver definition
abstract
final.
public section.
class-methods: drive_process
importing
row_count
type data_exchangeable=>row_counter
recipient
type data_exchangeable=>email_recipient
.
endclass.
class process_driver implementation.
method drive_process.
endmethod.
endclass.
At this point the syntax check still passes, but the class does nothing. Now move all the statements found in the start-of-selection event block into the new method drive_process, changing the names of the fields assigned as parameter values as necessary, as in:
method drive_process.
call method flight_report=>produce_report exporting row_count = row_count.
call method carrier_report=>produce_report exporting row_count = row_count.
call method booking_report=>produce_report exporting row_count = row_count.
call method excel_spreadsheet_manager=>send_excel_via_email exporting recipient = recipient.
endmethod.
At this point a syntax check should reveal these warnings for each of the three statements in method drive_process invoking methods produce_report:
The exception ZCX_EXCEL is neither caught nor is it declared in the RAISING clause of “DRIVE_PROCESS”.
These warnings occur because the new drive_process method is invoking three other methods that indicate in their signatures they raise exception zcx_excel, but method drive_process indicates neither that it raises this exception itself nor that it checks for this exception being raised by the methods it invokes. To resolve these warnings, surround the calls to the produce_report methods with a try-entry block with a catch for exception zcx_excel, as in:
method drive_process.
try.
call method flight_report=>produce_report exporting row_count = row_count.
call method carrier_report=>produce_report exporting row_count = row_count.
call method booking_report=>produce_report exporting row_count = row_count.
catch zcx_excel ##NO_HANDLER.
endtry.
call method excel_spreadsheet_manager=>send_excel_via_email exporting recipient = recipient.
endmethod.
Now a syntax check will pass again, however the program will do nothing because the start-of-selection event block is now empty. Correct this by including a call to the new drive_process method from within the start-of-selection event block:
start-of-selection.
call method process_driver=>drive_process
exporting
row_count = rowcount
recipient = recipien.
Executing the program at this point should prove that it still works as before.
Rule 6.44: No Implementation in Dialog Modules and Event Blocks
Only use dialog modules and event blocks if they’re technically necessary. Do not implement the required function there. Instead, call appropriate (local) methods.
By this rule we also soon realize that our initialization event block contains the implementation of its required function. So, let’s correct this by declaring a new local static class named email_address_resolver defined with a single static method named resolve_email_address whose signature can accept an SAP userid and pass back its corresponding email address. Here is the skeleton of its code:
class email_address_resolver definition
abstract
final.
public section.
class-methods: resolve_email_address
importing
userid
type syuname
exporting
email_address
type data_exchangeable=>email_recipient
.
endclass.
class email_address_resolver implementation.
method resolve_email_address.
endmethod.
endclass.
Place this just ahead of the parameters statement defining rowcount. The syntax check still passes.
Then move the code from the initialization event block into the new method, applying the following changes:
◈ Change recipien to email_address.
◈ Change sy-uname to userid.
The syntax check still passes, but there no longer is any email address resolution for the initial selection screen, so place a call to this method in the initialization event block where the former statements had been removed:
initialization.
call method email_address_resolver=>resolve_email_address
exporting
userid = sy-uname
importing
email_address = recipien.
Executing the program at this point should prove that it still works as before.
We’ve made enough changes for now and the final image of the code looks like this:
report.
interface data_exchangeable.
types : row_counter type n length 02.
types : email_recipient
type adr6-smtp_addr.
endinterface.
class excel_spreadsheet_manager definition
abstract
final.
public section.
class-methods: copy_table_to_excel_worksheet
importing
source_stack
type standard table
source_description
type string
raising
zcx_excel
, send_excel_via_email
importing
recipient
type data_exchangeable=>email_recipient
.
private section.
class-data : excel type ref to zcl_excel.
endclass.
class excel_spreadsheet_manager implementation.
method copy_table_to_excel_worksheet.
constants : first_column type char1 value 'A'
.
data : worksheet type ref to zcl_excel_worksheet
, worksheet_title
type zexcel_sheet_title
, table_settings type zexcel_s_table_settings
.
table_settings-table_style = zcl_excel_table=>builtinstyle_medium2.
table_settings-show_row_stripes
= abap_true.
table_settings-nofilters = abap_true.
table_settings-top_left_column
= first_column.
table_settings-top_left_row = 01.
if excel is not bound.
create object excel.
worksheet = excel->get_active_worksheet( ).
else.
worksheet = excel->add_new_worksheet( ).
endif.
worksheet_title = source_description.
worksheet->set_title( worksheet_title ).
worksheet->bind_table(
ip_table = source_stack
is_table_settings = table_settings
).
endmethod.
method send_excel_via_email.
constants : excel_file_type
type string value '.xlsx'
, file_name_parameter
type string value '&SO_FILENAME='
.
data : excel_writer type ref to zif_excel_writer
, excel_as_xstring
type xstring
, excel_as_xstring_bytecount
type i
, excel_as_solix_stack
type solix_tab
, mail_send_request
type ref to cl_bcs
, mail_message type ref to cl_document_bcs
, any_bcs_exception
type ref to cx_bcs
, diagnostic type string
, mail_title type so_obj_des
, mail_text_stack
type soli_tab
, mail_text_entry
like line
of mail_text_stack
, mail_attachment_subject
type sood-objdes
, mail_attachment_bytecount
type sood-objlen
, mail_attachment_header_stack
type soli_tab
, mail_attachment_header_entry
like line of mail_attachment_header_stack
, internet_email_recipient
type ref to if_recipient_bcs
, successful_send
type abap_bool
, file_name type string
.
" Much of the code here was lifted from method send_mail of
" class lcl_ouput, defined in object ZDEMO_EXCEL_OUTPUTOPT_INCL:
concatenate sy-repid " this report name
sy-datum " current date
sy-uzeit " current time
excel_file_type " excel file extension
into file_name.
mail_title = file_name.
mail_attachment_subject = file_name.
mail_text_entry = 'See attachment'.
append mail_text_entry
to mail_text_stack.
concatenate file_name_parameter
file_name
into mail_attachment_header_entry.
append mail_attachment_header_entry
to mail_attachment_header_stack.
create object excel_writer type zcl_excel_writer_2007.
excel_as_xstring = excel_writer->write_file( excel ).
excel_as_solix_stack = cl_bcs_convert=>xstring_to_solix( iv_xstring = excel_as_xstring ).
excel_as_xstring_bytecount = xstrlen( excel_as_xstring ).
mail_attachment_bytecount = excel_as_xstring_bytecount.
try.
mail_message = cl_document_bcs=>create_document(
i_type = 'RAW' "#EC NOTEXT
i_text = mail_text_stack
i_subject = mail_title
).
mail_message->add_attachment(
i_attachment_type = 'XLS' "#EC NOTEXT
i_attachment_subject = mail_attachment_subject
i_attachment_size = mail_attachment_bytecount
i_att_content_hex = excel_as_solix_stack
i_attachment_header = mail_attachment_header_stack
).
mail_send_request = cl_bcs=>create_persistent( ).
mail_send_request->set_document( mail_message ).
internet_email_recipient = cl_cam_address_bcs=>create_internet_address( recipient ).
mail_send_request->add_recipient( internet_email_recipient ).
successful_send = mail_send_request->send( ).
commit work.
if successful_send eq abap_false.
message i500(sbcoms) with recipient.
else.
message s022(so).
message 'Document ready to be sent - Check SOST' type 'I'.
endif.
catch cx_bcs into any_bcs_exception.
diagnostic = any_bcs_exception->if_message~get_text( ).
message diagnostic type 'I'.
endtry.
endmethod.
endclass.
class flight_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class flight_report implementation.
method produce_report.
data : flight_stack type standard table of sflight
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = flight_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table flight_stack
from sflight
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
endmethod.
endclass.
class carrier_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class carrier_report implementation.
method produce_report.
data : carrier_stack type standard table of scarr
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = carrier_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table carrier_stack
from scarr
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = carrier_stack
source_description = 'Carriers'.
endmethod.
endclass.
class booking_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class booking_report implementation.
method produce_report.
data : booking_stack type standard table of sbook
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = booking_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table booking_stack
from sbook
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = booking_stack
source_description = 'Bookings'.
endmethod.
endclass.
class process_driver definition
abstract
final.
public section.
class-methods: drive_process
importing
row_count
type data_exchangeable=>row_counter
recipient
type data_exchangeable=>email_recipient
.
endclass.
class process_driver implementation.
method drive_process.
try.
call method flight_report=>produce_report exporting row_count = row_count.
call method carrier_report=>produce_report exporting row_count = row_count.
call method booking_report=>produce_report exporting row_count = row_count.
catch zcx_excel ##NO_HANDLER.
endtry.
call method excel_spreadsheet_manager=>send_excel_via_email exporting recipient = recipient.
endmethod.
endclass.
class email_address_resolver definition
abstract
final.
public section.
class-methods: resolve_email_address
importing
userid
type syuname
exporting
email_address
type data_exchangeable=>email_recipient
.
endclass.
class email_address_resolver implementation.
method resolve_email_address.
select single smtp_addr
into email_address
from adr6 ##WARN_OK
inner join
usr21 on usr21~persnumber eq adr6~persnumber
where usr21~bname eq userid.
endmethod.
endclass.
parameters : rowcount type data_exchangeable=>row_counter.
parameters : recipien type data_exchangeable=>email_recipient.
initialization.
call method email_address_resolver=>resolve_email_address
exporting
userid = sy-uname
importing
email_address = recipien.
start-of-selection.
call method process_driver=>drive_process
exporting
row_count = rowcount
recipient = recipien.
Notice that now all processing has been encapsulated into classes. The non-object-oriented portion of our program has been reduced to the final 13 statements, which consists solely of two screen parameter definitions and two classic ABAP event blocks, each event block now having only a single call to a method of a local class. Notice also that all of the data definitions defined externally to the classes – specifically, the two parameters statements – are defined to reference types provided by the local interface. Not only have we succeeded in removing global variables but we also have made the program much more maintainable by encapsulating the processing into appropriately named classes and interfaces.
To recap from the preceding blog, the ABAP program presents 3 ALV reports, one after the other using the records of tables SFLIGHT, SCARR and SBOOK, respectively, converting the content of each report into a separate worksheet of an Excel spreadsheet, and then sends the spreadsheet to our own internet email address. Here is the original procedural source code as we left it in the previous blog:
types : row_counter type n length 02.
types : email_recipient
type adr6-smtp_addr.
data : excel type ref to zcl_excel ##NEEDED.
parameters : rowcount type row_counter.
parameters : recipien type email_recipient.
initialization.
select single smtp_addr
into recipien
from adr6 ##WARN_OK
inner join
usr21 on usr21~persnumber eq adr6~persnumber
where usr21~bname eq sy-uname.
start-of-selection.
perform display_flight_rows using rowcount.
perform display_carrier_rows using rowcount.
perform display_booking_rows using rowcount.
perform send_excel_via_email using recipien.
form display_flight_rows using row_count
type row_counter
raising zcx_excel.
data : flight_stack type standard table of sflight
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = flight_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table flight_stack
from sflight
up to row_count rows.
alv_report->display( ).
perform copy_table_to_excel_worksheet using flight_stack 'Flights'.
endform.
form display_carrier_rows using row_count
type row_counter
raising zcx_excel.
data : carrier_stack type standard table of scarr
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = carrier_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table carrier_stack
from scarr
up to row_count rows.
alv_report->display( ).
perform copy_table_to_excel_worksheet using carrier_stack 'Carriers'.
endform.
form display_booking_rows using row_count
type row_counter
raising zcx_excel.
data : booking_stack type standard table of sbook
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = booking_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table booking_stack
from sbook
up to row_count rows.
alv_report->display( ).
perform copy_table_to_excel_worksheet using booking_stack 'Bookings'.
endform.
form copy_table_to_excel_worksheet using source_stack
type standard table
source_description
type string
raising zcx_excel.
constants : first_column type char1 value 'A'
.
data : worksheet type ref to zcl_excel_worksheet
, worksheet_title
type zexcel_sheet_title
, table_settings type zexcel_s_table_settings
.
table_settings-table_style = zcl_excel_table=>builtinstyle_medium2.
table_settings-show_row_stripes
= abap_true.
table_settings-nofilters = abap_true.
table_settings-top_left_column
= first_column.
table_settings-top_left_row = 01.
if excel is not bound.
create object excel.
worksheet = excel->get_active_worksheet( ).
else.
worksheet = excel->add_new_worksheet( ).
endif.
worksheet_title = source_description.
worksheet->set_title( worksheet_title ).
worksheet->bind_table(
ip_table = source_stack
is_table_settings = table_settings
).
endform.
form send_excel_via_email using recipient type email_recipient.
constants : excel_file_type
type string value '.xlsx'
, file_name_parameter
type string value '&SO_FILENAME='
.
data : excel_writer type ref to zif_excel_writer
, excel_as_xstring
type xstring
, excel_as_xstring_bytecount
type i
, excel_as_solix_stack
type solix_tab
, mail_send_request
type ref to cl_bcs
, mail_message type ref to cl_document_bcs
, any_bcs_exception
type ref to cx_bcs
, diagnostic type string
, mail_title type so_obj_des
, mail_text_stack
type soli_tab
, mail_text_entry
like line
of mail_text_stack
, mail_attachment_subject
type sood-objdes
, mail_attachment_bytecount
type sood-objlen
, mail_attachment_header_stack
type soli_tab
, mail_attachment_header_entry
like line of mail_attachment_header_stack
, internet_email_recipient
type ref to if_recipient_bcs
, successful_send
type abap_bool
, file_name type string
.
" Much of the code here was lifted from method send_mail of
" class lcl_ouput, defined in object ZDEMO_EXCEL_OUTPUTOPT_INCL:
concatenate sy-repid " this report name
sy-datum " current date
sy-uzeit " current time
excel_file_type " excel file extension
into file_name.
mail_title = file_name.
mail_attachment_subject = file_name.
mail_text_entry = 'See attachment'.
append mail_text_entry
to mail_text_stack.
concatenate file_name_parameter
file_name
into mail_attachment_header_entry.
append mail_attachment_header_entry
to mail_attachment_header_stack.
create object excel_writer type zcl_excel_writer_2007.
excel_as_xstring = excel_writer->write_file( excel ).
excel_as_solix_stack = cl_bcs_convert=>xstring_to_solix( iv_xstring = excel_as_xstring ).
excel_as_xstring_bytecount = xstrlen( excel_as_xstring ).
mail_attachment_bytecount = excel_as_xstring_bytecount.
try.
mail_message = cl_document_bcs=>create_document(
i_type = 'RAW' "#EC NOTEXT
i_text = mail_text_stack
i_subject = mail_title
).
mail_message->add_attachment(
i_attachment_type = 'XLS' "#EC NOTEXT
i_attachment_subject = mail_attachment_subject
i_attachment_size = mail_attachment_bytecount
i_att_content_hex = excel_as_solix_stack
i_attachment_header = mail_attachment_header_stack
).
mail_send_request = cl_bcs=>create_persistent( ).
mail_send_request->set_document( mail_message ).
internet_email_recipient = cl_cam_address_bcs=>create_internet_address( recipient ).
mail_send_request->add_recipient( internet_email_recipient ).
successful_send = mail_send_request->send( ).
commit work.
if successful_send eq abap_false.
message i500(sbcoms) with recipient.
else.
message s022(so).
message 'Document ready to be sent - Check SOST' type 'I'.
endif.
catch cx_bcs into any_bcs_exception.
diagnostic = any_bcs_exception->if_message~get_text( ).
message diagnostic type 'I'.
endtry.
endform.
Open your favorite ABAP editor, make a copy of this ABAP program and follow along as we apply changes to transform it from a procedural design into an object-oriented design using local classes. For those who do not have ABAP2XLSX available at their site, make the following changes to the source code:
◈ change the definition of field excel from:
data : excel type ref to zcl_excel ##NEEDED.
to
data : excel type string ##NEEDED.
◈ replace the two subroutines copy_table_to_excel_worksheet and send_excel_via_email appearing at the end of the program with the following code:
form copy_table_to_excel_worksheet using source_stack
type standard table
source_description
type string
raising zcx_excel.
data : source_stack_lines type string.
describe table source_stack lines source_stack_lines.
concatenate excel
source_stack_lines
source_description
into excel separated by space.
endform.
form send_excel_via_email using recipient type email_recipient.
data : message type string.
concatenate excel
'would be sent to'
recipient
into message separated by space.
message message type 'I'.
endform.
Getting started
First, we need to review what our program does and to identify the types of classes that would facilitate its processing. This program produces 3 ALV reports, presents each one to the user, one after the other, and then includes the report content in an Excel spreadsheet included as an attachment to an email sent across the internet. So, if we were to describe the entities that we would need to facilitate this we might find we have the following:
◈ flight_report
◈ carrier_report
◈ booking_report
◈ excel_spreadsheet_manager
One of the basic principles of OO design is known as Abstraction, and often it is explained in the context of a class being an abstraction to represent some real-world counterpart. The real-world counterparts of our program are the 3 reports and the Excel spreadsheet manager. Accordingly, we shall be defining 4 OO local classes with these names. Another of the basic principles of OO design is known as Encapsulation, and once we define these classes we will have each of them provide all the processing associated with the real-world entity it represents.
Let’s start with this and see how we might go about changing the code to achieve an OO design. I like to structure a report containing local OO classes such that the classes appear first, followed by any classic ABAP statements, such as parameters, select-options and event blocks. This promotes good component encapsulation since the classes cannot possibly refer to any data defining statements that could appear ahead of them (since there are none), making them easily separable from the report if we were to decide later to transform them into global classes.
First, we’ll define a local OO class to describe the flight_report. We’ll keep this simple for now and define it as a static class, meaning that to use it will not require that we instantiate the class. It should contain a single static method named produce_report whose signature is comparable to the signature defined for subroutine display_flight_rows. The skeleton of the class might look like this:
class flight_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type row_counter
raising
zcx_excel
.
endclass.
class flight_report implementation.
method produce_report.
endmethod.
endclass.
Place these lines immediately after the report statement. At this point the syntax check fails due to a reference to the unknown row_counter in the signature of the produce_report method. Indeed, this is defined by a types statement immediately following the final endclass statement defining this new flight_report class, so cut and paste this types statement ahead of the flight_report class.
The syntax check now passes again, but our new local class also now contains a reference to a global types statement defined in this program. If later we were to decide to convert local class flight_report into a global class, we’d need to resolve this reference to a component defined within this program. Let’s resolve this now by encapsulating this types statement in a local OO interface enabling us to exchange data between entities in this program, an interface we’ll call data_exchangeable. Do this by surrounding the types statement defining row_counter with the following two statements:
interface data_exchangeable.
endinterface.
This again results in a syntax error due to a reference to the unknown row_counter in the signature of the produce_report method, so prefix the reference to row_counter in this signature with its interface qualifier “data_exchangable=>”, as in:
importing
row_count
type data_exchangeable=>row_counter
At this point a syntax check will still fail due to all the other references throughout the program having a reference to a now-undefined type row_counter. Resolve this now for each of these locations similarly by prefixing the reference to row_counter with its interface qualifier. You should find this necessary at four more locations in the code before the syntax check finally passes.
Fine, we did all that, but why, you might be wondering, did we bother encapsulating the types statement for row_counter into a local interface? The answer is that if now we decide to convert local class flight_report into a global_class, its only reference to an entity defined in this program is an entity now contained in a local interface, so globalizing the flight_report class now would require that we also globalize the data_exchangeable interface it references.
Although the syntax check now passes, the new class flight_report has nothing to do because its method produce_report is empty, so now do the following:
◈ Move the code appearing between form display_flight_rows and its corresponding endform into method produce_report.
◈ Remove the corresponding form and endform statements.
This will cause a syntax check failure for the statement calling the now-missing subroutine, so replace statement
perform display_flight_rows using rowcount.
with statement
call method flight_report=>produce_report exporting row_count = rowcount.
Now a syntax check will pass again. Finally, we have succeeded in defining our first local class. Executing the program at this point should prove that it still works as before.
Defining a second local class
Follow the same process we used for creating class flight_report to transform the form-endform subroutine display_carrier_rows into its equivalent local class carrier_report, placing this class after class flight_report. Executing the program at this point should prove that it still works as before.
Defining a third local class
After that is completed, follow that same process again to transform the form-endform subroutine display_booking_rows into its equivalent local class booking_report, placing this class after class carrier_report. Executing the program at this point should prove that it still works as before.
Taking a moment for review
So far, we have taken a procedural program and transformed it by adding to it some local object-oriented classes and interfaces. Specifically, we’ve added three local static classes, each one following the same basic model as the other two, and one local interface. With this version we have three report classes representing their real-world counterparts, adhering to the object-oriented principle of Abstraction. In addition, we have inserted into each of these three report classes all the processing associated with producing its respective report, adhering to the object-oriented principle of Encapsulation.
At this point we still have two form-endform subroutines remaining in the program and we already have identified one other local class that we have yet to implement.
Defining a fourth local class
We made some progress but one of the things still glaringly apparent in this program is its use of a global variable defined in the data statement shown below:
types : email_recipient
type adr6-smtp_addr.
data : excel type ref to zcl_excel ##NEEDED.
parameters : rowcount type row_counter.
parameters : recipien type email_recipient.
Good object-oriented design would suggest that we should strive to eliminate the use of global variables except where they are absolutely necessary. A good example of a global variable that is absolutely necessary is any field defined to appear on a GUI screen, such as the parameter statements shown above. However, GUI screens cannot be defined as part of ABAP OO classes – other techniques are used instead. Meanwhile, the data field named excel is a good example of the definition of a global variable that should be avoided.
Create the skeleton for the new local class excel_spreadsheet_manager, for now placing it after local class booking_report. It should contain two static methods, each named the same as one of the two remaining form-endform subroutines (copy_table_to_excel_worksheet; send_excel_via_email), with each method having a signature comparable to the signature defined for its counterpart subroutine. Here is its code:
class excel_spreadsheet_manager definition
abstract
final.
public section.
class-methods: copy_table_to_excel_worksheet
importing
source_stack
type standard table
source_description
type string
raising
zcx_excel
, send_excel_via_email
importing
recipient
type email_recipient
.
endclass.
class excel_spreadsheet_manager implementation.
method copy_table_to_excel_worksheet.
endmethod.
method send_excel_via_email.
endmethod.
endclass.
At this point the syntax check fails due to email_recipient being undefined. To resolve this issue, let’s move the types statement for email_recipient to immediately follow the report statement. This causes a syntax check to pass again but notice that yet again we have a global types statement preceding the definitions of our local classes.
Since email_recipient is defined on a types statement and facilitates communication between various entities in this program, serving a similar purpose to the way row_count was defined, which we had moved into the data_exchangeable local interface, let’s also move the types definition for email_recipient to the end of the data_exchangeable local interface and similarly prefix all references to email_recipient with its interface qualifier “data_exchangable=>”. Afterward a syntax will pass.
As we saw when we did this with class flight_report, we have made changes so that the only reference by new class excel_spreadsheet_manager to an entity defined in this program is an entity now contained in a local interface, and if we were to decide to convert local class excel_spreadsheet_manager to a global class we also would need to convert local interface data_exchangeable to a global interface along with it.
Now move the code from their form-endform counterparts into the new methods for copy_table_to_excel_worksheet and send_excel_via_email, respectively, retaining the empty form-endform statements. Perform a syntax check at this point and you will see that the global field named excel is not available to method copy_table_to_excel_worksheet. Indeed, this global field is defined further on in the code, so we should expect this to be the case, but more to the point is that we want this field no longer to be defined as a global variable but defined as an attribute of class excel_spreadsheet_manager, so do the following:
◈ Define a private section for the new class excel_spreadsheet_manager.
◈ Move the global variable named excel into this private section and define it as a static attribute (class-data).
◈ Discard the ##NEEDED pragma from definition for attribute excel, no longer necessary since the field no longer is defined globally.
It should look like this:
private section.
class-data : excel type ref to zcl_excel.
endclass.
or, if ABAP2XLSX is not available at your site, like this:
private section.
class-data : excel type string.
endclass.
At this point the syntax check passes again, but we still have references to the empty form-endform routines, so let’s remove these empty subroutines and replace the calls to them with calls to the corresponding methods of local class excel_spreadsheet_manager, as in:
◈ Replace statement
perform copy_table_to_excel_worksheet using flight_stack 'Flights'.
with statement
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
◈ Replace statement
perform copy_table_to_excel_worksheet using carrier_stack 'Carriers'.
with statement
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = carrier_stack
source_description = 'Carriers'.
◈ Replace statement
perform copy_table_to_excel_worksheet using booking_stack 'Bookings'.
with statement
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = booking_stack
source_description = 'Bookings'.
◈ Replace statement
perform send_excel_via_email using recipien.
with statement
call method excel_spreadsheet_manager=>send_excel_via_email exporting recipient = recipien.
At this point a syntax check will fail for the first replacing call method statement shown above indicating:
Type “EXCEL_SPREADSHEET_MANAGER” is unknown
This is because the definition of the excel_spreadsheet_manager class follows the reference to it by this statement in the flight_report class. This is a consequence of the ABAP compiler being a single-pass complier, meaning that it resolves references as it encounters them while making only a single pass through the source code. Accordingly, any local data and class definitions must precede any references made to them. So here is a case where the local class excel_spreadsheet_manager needs to precede any classes using it. The solution is to relocate the excel_spreadsheet_manager class to appear ahead of any references to it, so cut and paste this local class immediately after the local interface.
After relocating this class the syntax check now passes again. Executing the program at this point should prove that it still works as before.
Notice that we’ve eliminated the global variable and very few lines remain outside of our local OO classes and interfaces – only the final 14 statements.
Defining a fifth local class
Since we have declared that our process is to present the 3 ALV reports and then send the content as a spreadsheet via email, we also shall define a local OO class to facilitate this processing, called process_driver. Accordingly, the next thing we’re going to do is to consolidate all the method calls in the start-of-selection event block into a single call to a new method defined in the new class process_driver. We’ll name the new method drive_process, whose signature should be capable of accepting a row count and an email recipient value corresponding to the values the user would provide in the two parameters appearing on the initial selection screen. First let’s define the class skeleton, placing it between the end of the booking_report class and the parameters statement defining rowcount:
class process_driver definition
abstract
final.
public section.
class-methods: drive_process
importing
row_count
type data_exchangeable=>row_counter
recipient
type data_exchangeable=>email_recipient
.
endclass.
class process_driver implementation.
method drive_process.
endmethod.
endclass.
At this point the syntax check still passes, but the class does nothing. Now move all the statements found in the start-of-selection event block into the new method drive_process, changing the names of the fields assigned as parameter values as necessary, as in:
method drive_process.
call method flight_report=>produce_report exporting row_count = row_count.
call method carrier_report=>produce_report exporting row_count = row_count.
call method booking_report=>produce_report exporting row_count = row_count.
call method excel_spreadsheet_manager=>send_excel_via_email exporting recipient = recipient.
endmethod.
At this point a syntax check should reveal these warnings for each of the three statements in method drive_process invoking methods produce_report:
The exception ZCX_EXCEL is neither caught nor is it declared in the RAISING clause of “DRIVE_PROCESS”.
These warnings occur because the new drive_process method is invoking three other methods that indicate in their signatures they raise exception zcx_excel, but method drive_process indicates neither that it raises this exception itself nor that it checks for this exception being raised by the methods it invokes. To resolve these warnings, surround the calls to the produce_report methods with a try-entry block with a catch for exception zcx_excel, as in:
method drive_process.
try.
call method flight_report=>produce_report exporting row_count = row_count.
call method carrier_report=>produce_report exporting row_count = row_count.
call method booking_report=>produce_report exporting row_count = row_count.
catch zcx_excel ##NO_HANDLER.
endtry.
call method excel_spreadsheet_manager=>send_excel_via_email exporting recipient = recipient.
endmethod.
Now a syntax check will pass again, however the program will do nothing because the start-of-selection event block is now empty. Correct this by including a call to the new drive_process method from within the start-of-selection event block:
start-of-selection.
call method process_driver=>drive_process
exporting
row_count = rowcount
recipient = recipien.
Executing the program at this point should prove that it still works as before.
Rule 6.44: No Implementation in Dialog Modules and Event Blocks
Only use dialog modules and event blocks if they’re technically necessary. Do not implement the required function there. Instead, call appropriate (local) methods.
By this rule we also soon realize that our initialization event block contains the implementation of its required function. So, let’s correct this by declaring a new local static class named email_address_resolver defined with a single static method named resolve_email_address whose signature can accept an SAP userid and pass back its corresponding email address. Here is the skeleton of its code:
class email_address_resolver definition
abstract
final.
public section.
class-methods: resolve_email_address
importing
userid
type syuname
exporting
email_address
type data_exchangeable=>email_recipient
.
endclass.
class email_address_resolver implementation.
method resolve_email_address.
endmethod.
endclass.
Place this just ahead of the parameters statement defining rowcount. The syntax check still passes.
Then move the code from the initialization event block into the new method, applying the following changes:
◈ Change recipien to email_address.
◈ Change sy-uname to userid.
The syntax check still passes, but there no longer is any email address resolution for the initial selection screen, so place a call to this method in the initialization event block where the former statements had been removed:
initialization.
call method email_address_resolver=>resolve_email_address
exporting
userid = sy-uname
importing
email_address = recipien.
Executing the program at this point should prove that it still works as before.
Summary
We’ve made enough changes for now and the final image of the code looks like this:
report.
interface data_exchangeable.
types : row_counter type n length 02.
types : email_recipient
type adr6-smtp_addr.
endinterface.
class excel_spreadsheet_manager definition
abstract
final.
public section.
class-methods: copy_table_to_excel_worksheet
importing
source_stack
type standard table
source_description
type string
raising
zcx_excel
, send_excel_via_email
importing
recipient
type data_exchangeable=>email_recipient
.
private section.
class-data : excel type ref to zcl_excel.
endclass.
class excel_spreadsheet_manager implementation.
method copy_table_to_excel_worksheet.
constants : first_column type char1 value 'A'
.
data : worksheet type ref to zcl_excel_worksheet
, worksheet_title
type zexcel_sheet_title
, table_settings type zexcel_s_table_settings
.
table_settings-table_style = zcl_excel_table=>builtinstyle_medium2.
table_settings-show_row_stripes
= abap_true.
table_settings-nofilters = abap_true.
table_settings-top_left_column
= first_column.
table_settings-top_left_row = 01.
if excel is not bound.
create object excel.
worksheet = excel->get_active_worksheet( ).
else.
worksheet = excel->add_new_worksheet( ).
endif.
worksheet_title = source_description.
worksheet->set_title( worksheet_title ).
worksheet->bind_table(
ip_table = source_stack
is_table_settings = table_settings
).
endmethod.
method send_excel_via_email.
constants : excel_file_type
type string value '.xlsx'
, file_name_parameter
type string value '&SO_FILENAME='
.
data : excel_writer type ref to zif_excel_writer
, excel_as_xstring
type xstring
, excel_as_xstring_bytecount
type i
, excel_as_solix_stack
type solix_tab
, mail_send_request
type ref to cl_bcs
, mail_message type ref to cl_document_bcs
, any_bcs_exception
type ref to cx_bcs
, diagnostic type string
, mail_title type so_obj_des
, mail_text_stack
type soli_tab
, mail_text_entry
like line
of mail_text_stack
, mail_attachment_subject
type sood-objdes
, mail_attachment_bytecount
type sood-objlen
, mail_attachment_header_stack
type soli_tab
, mail_attachment_header_entry
like line of mail_attachment_header_stack
, internet_email_recipient
type ref to if_recipient_bcs
, successful_send
type abap_bool
, file_name type string
.
" Much of the code here was lifted from method send_mail of
" class lcl_ouput, defined in object ZDEMO_EXCEL_OUTPUTOPT_INCL:
concatenate sy-repid " this report name
sy-datum " current date
sy-uzeit " current time
excel_file_type " excel file extension
into file_name.
mail_title = file_name.
mail_attachment_subject = file_name.
mail_text_entry = 'See attachment'.
append mail_text_entry
to mail_text_stack.
concatenate file_name_parameter
file_name
into mail_attachment_header_entry.
append mail_attachment_header_entry
to mail_attachment_header_stack.
create object excel_writer type zcl_excel_writer_2007.
excel_as_xstring = excel_writer->write_file( excel ).
excel_as_solix_stack = cl_bcs_convert=>xstring_to_solix( iv_xstring = excel_as_xstring ).
excel_as_xstring_bytecount = xstrlen( excel_as_xstring ).
mail_attachment_bytecount = excel_as_xstring_bytecount.
try.
mail_message = cl_document_bcs=>create_document(
i_type = 'RAW' "#EC NOTEXT
i_text = mail_text_stack
i_subject = mail_title
).
mail_message->add_attachment(
i_attachment_type = 'XLS' "#EC NOTEXT
i_attachment_subject = mail_attachment_subject
i_attachment_size = mail_attachment_bytecount
i_att_content_hex = excel_as_solix_stack
i_attachment_header = mail_attachment_header_stack
).
mail_send_request = cl_bcs=>create_persistent( ).
mail_send_request->set_document( mail_message ).
internet_email_recipient = cl_cam_address_bcs=>create_internet_address( recipient ).
mail_send_request->add_recipient( internet_email_recipient ).
successful_send = mail_send_request->send( ).
commit work.
if successful_send eq abap_false.
message i500(sbcoms) with recipient.
else.
message s022(so).
message 'Document ready to be sent - Check SOST' type 'I'.
endif.
catch cx_bcs into any_bcs_exception.
diagnostic = any_bcs_exception->if_message~get_text( ).
message diagnostic type 'I'.
endtry.
endmethod.
endclass.
class flight_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class flight_report implementation.
method produce_report.
data : flight_stack type standard table of sflight
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = flight_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table flight_stack
from sflight
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
endmethod.
endclass.
class carrier_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class carrier_report implementation.
method produce_report.
data : carrier_stack type standard table of scarr
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = carrier_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table carrier_stack
from scarr
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = carrier_stack
source_description = 'Carriers'.
endmethod.
endclass.
class booking_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class booking_report implementation.
method produce_report.
data : booking_stack type standard table of sbook
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = booking_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table booking_stack
from sbook
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = booking_stack
source_description = 'Bookings'.
endmethod.
endclass.
class process_driver definition
abstract
final.
public section.
class-methods: drive_process
importing
row_count
type data_exchangeable=>row_counter
recipient
type data_exchangeable=>email_recipient
.
endclass.
class process_driver implementation.
method drive_process.
try.
call method flight_report=>produce_report exporting row_count = row_count.
call method carrier_report=>produce_report exporting row_count = row_count.
call method booking_report=>produce_report exporting row_count = row_count.
catch zcx_excel ##NO_HANDLER.
endtry.
call method excel_spreadsheet_manager=>send_excel_via_email exporting recipient = recipient.
endmethod.
endclass.
class email_address_resolver definition
abstract
final.
public section.
class-methods: resolve_email_address
importing
userid
type syuname
exporting
email_address
type data_exchangeable=>email_recipient
.
endclass.
class email_address_resolver implementation.
method resolve_email_address.
select single smtp_addr
into email_address
from adr6 ##WARN_OK
inner join
usr21 on usr21~persnumber eq adr6~persnumber
where usr21~bname eq userid.
endmethod.
endclass.
parameters : rowcount type data_exchangeable=>row_counter.
parameters : recipien type data_exchangeable=>email_recipient.
initialization.
call method email_address_resolver=>resolve_email_address
exporting
userid = sy-uname
importing
email_address = recipien.
start-of-selection.
call method process_driver=>drive_process
exporting
row_count = rowcount
recipient = recipien.
Notice that now all processing has been encapsulated into classes. The non-object-oriented portion of our program has been reduced to the final 13 statements, which consists solely of two screen parameter definitions and two classic ABAP event blocks, each event block now having only a single call to a method of a local class. Notice also that all of the data definitions defined externally to the classes – specifically, the two parameters statements – are defined to reference types provided by the local interface. Not only have we succeeded in removing global variables but we also have made the program much more maintainable by encapsulating the processing into appropriately named classes and interfaces.
SAP Business One provides instant access to critical business information and accurate result oriented insights to the owners and their employees so that they can make informed business decisions like never before.
ReplyDeletevery nice information provided thanks for sharing for more do visitSAP HANA Training in Texas
ReplyDeletesap business article provides instant access to oriented to the owners can make informated business thanks for sharing for visit SAP ABAP Training in San Antonio SAP ABAP Training in San Antonio
ReplyDelete