Collection of classes and demo program to generate HTML code using ABAP. Generated HTML can be used to compose a well-formatted email with table and images.
Introduction
ZCL_HTML_ELEMENT is base class. Rest of the classes inherits from ZCL_HTML_ELEMENT. Method to_html of the class returns the HTML code including all nodes under the hierarchy. I have included source code of all classes later in the blog for you to copy if you decide to use these.
I have recently used these classes while sending email notifications from Purchase Order release workflow. Emails sent were well formatted with header and line item details rendered using table tag.
How to Use
1. Paragraph
To add paragraph to HTML document use class ZCL_HTML_PARAGRAPH. In its simplest form, you can create an instance of ZCL_HTML_PARAGRAPH passing text in importing parameter iv_value.
data(html) = new zcl_html_document( ) .
data(simple_paragraph) = new zcl_html_paragraph(
iv_value = 'Simple text' ) .
html->body->add_child( simple_paragraph ) .
cl_demo_output=>write_html( html->to_html( ) ) .
cl_demo_output=>display( ) .
To include long text, for example, Purchase order header text use method set_long_text specifying usual information to identify long text.
data(html_paragraph) = new zcl_html_paragraph( ) .
html_paragraph->set_long_text(
exporting
iv_id = 'F02'
iv_name = po_header-ebeln
iv_object = 'EKKO' ).
html->body->add_child( html_paragraph ) .
2. Structure
To add values from a structure in HTML document use class ZCL_HTML_TABLE calling method set_structure_data passing structure variable. You can optionally pass comma-separated list of fields to be included in HTML in parameter iv_visible_column.
select single from ekko fields *
where ebeln = '4500000014' into @data(po_header) .
data(html_po_head) = new zcl_html_table( ).
html_po_head->set_struct_data(
is_structure = po_header
iv_visible_fields = 'ebeln,lifnr' ) .
html->body->add_child( html_po_head ) .
Class picks field header from dictionary so make sure you have a structure with fields defined with reference to the dictionary. Note that fields headers are rendered in the first column. If you prefer field header to be rendered as the first row then refer next section.
3. Table with Single line
In case you have structure which you like to display as below screen shot then create internal table of the structure and use method SET_TABLE_DATA of class ZCL_HTML_TABLE.
select from ekko fields * where ebeln = '4500000014'
into table @data(po_head_line) .
data(table1) = new zcl_html_table( ).
table1->set_table_data(
it_table = po_head_line
iv_visible_columns = 'ebeln,lifnr' ) .
html->body->add_child( table1 ) .
4. Table with Multiple lines
select from ekpo fields *
where ebeln = '4500000014' into table @data(polines) .
data(table) = new zcl_html_table( ).
table->set_table_data(
it_table = polines
iv_visible_columns = 'ebeln,ebelp,txz01,menge,meins' ) .
html->body->add_child( table ) .
5. Image with Link
Use class ZCL_HTML_IMAGE to include image in HTML. To include an image which refers to url, pass image url which creating instance of class ZCL_HTML_IMAGE in parameter iv_src.
data(img) = new zcl_html_image(
iv_src = 'https://www.seekpng.com/png/full/382-3821698_hbo-logo-white-png-download-sap-logo-white.png'
iv_alt = 'SAP Logo' ).
img->set_width( '150' ).
html->body->add_child( img ) .
The HTML email with an image using URL reference are not downloaded automatically in MS Outlook. User will have to right-click on the image (or on email header) to download image(s). However, this results in email content with a smaller size.
There is however another (next section) way to embed image in HTML itself which will load the image without having the user to manually download it. This will though increase the email size as the image source is included as part of HTML.
6. Image with embedded content
To include mime image as part of html use method EMBEDD_MIME.
data(img_mime) = new zcl_html_image( iv_alt = 'mime embedded image' ) .
img_mime->embedd_mime(
iv_mime_path = '/SAP/PUBLIC/BOBF/BOB/bob_default_picture.png' ) .
img_mime->set_width( '150' ).
HTML generated will have image data inline rendering images in MS Outlook without having to download it manually.
Code
Class ZCL_HTML_ELEMENT
Base class ZCL_HTML_ELEMENT model the HTML element. Constructor let you create an element specifying tag, id, class and value. Additional attributes can be added using method add_attribute. Method to_html creates string by combining all variables and returns HTML string. The method to_html also include html for any child instance of HTML element added using method add_child.
class zcl_html_element definition
public
create public .
public section.
types:
tt_html type standard table of ref to zcl_html_element .
types: begin of ty_component,
name type string,
type type ref to cl_abap_typedescr,
end of ty_component.
types: tt_component type table of ty_component.
data id type string read-only .
data tag type string .
data attributes type /iwbep/t_mgw_name_value_pair read-only .
methods constructor
importing
!iv_tag type string
!iv_id type string optional
!iv_value type string optional
!iv_class type string optional .
methods to_html
returning
value(rv_value) type string .
methods has_child
returning
value(rv_value) type boolean .
methods add_child
importing
!html_element type ref to zcl_html_element .
methods add_attribute
importing
!iv_name type string
!iv_value type string .
methods get_value
returning
value(rv_value) type string .
methods set_value
importing
!iv_value type string .
protected section.
private section.
data children type tt_html .
data value type string .
endclass.
class zcl_html_element implementation.
method add_attribute.
append value #( name = iv_name value = iv_value ) to attributes .
endmethod.
method add_child.
append html_element to children .
endmethod.
method constructor.
tag = iv_tag .
value = iv_value .
if iv_id is not initial .
append value #( name = 'id' value = iv_id ) to attributes .
endif.
if iv_class is not initial .
append value #( name = 'class' value = iv_class ) to attributes .
endif.
endmethod.
method get_value.
rv_value = value .
endmethod.
method has_child.
if children is not initial .
rv_value = abap_true .
endif.
endmethod.
method set_value.
value = iv_value .
endmethod.
method to_html.
rv_value = |<{ tag }| < &&
reduce string( init att_html type string
for <attrib> in me->attributes
next att_html = att_html &&
| { <attrib>-name }="{ <attrib>-value }"| ) &&
|>| &&
value &&
reduce string( init child_html type string
for <child> in me->children
next child_html = child_html && <child>->to_html( ) ) &&
|</{ tag }>| && |\n| .
endmethod.
endclass.
Class ZCL_HTML_DOCUMENT
Class ZCL_HTML_DOCUMENT inherits from ZCL_HTML_ELEMENT. It creates <html> tag and tags <head> and <body> creating a hierarchy. You should use instance of this class to build document by adding futher elements to property body.
Method line_break added which you can use to add line break in HTML document.
class zcl_html_document definition
public
inheriting from zcl_html_element
final
create public .
public section.
data head type ref to zcl_html_element .
data body type ref to zcl_html_element .
methods constructor
importing
!iv_page_title type string .
methods line_break .
protected section.
private section.
endclass.
class zcl_html_document implementation.
method constructor.
super->constructor( exporting iv_tag = 'html' ) .
head = new zcl_html_element( iv_tag = 'head' iv_value = iv_page_title ) .
body = new zcl_html_element( iv_tag = 'body' ) .
me->add_child( head ) .
me->add_child( body ) .
endmethod.
method line_break.
body->add_child( new zcl_html_line_break( ) ) .
endmethod.
endclass.
Class ZCL_HTML_IMAGE
Class let you add image tag in HTML document specifying the URL of the image. Method embedd_mime let you add image from mime repository by embedding image data in HTML document itself.
class zcl_html_image definition
public
inheriting from zcl_html_element
final
create public .
public section.
methods constructor
importing
!iv_id type string optional
!iv_src type string optional
!iv_class type string optional
!iv_alt type string optional .
methods set_width
importing
!iv_value type csequence .
methods set_height
importing
!iv_value type csequence .
methods embedd_mime
importing
!iv_mime_path type csequence default '/SAP/PUBLIC/BOBF/BOB/BOB_DEFAULT_PICTURE.PNG' . "/SAP/PUBLIC/BOBF/BOB/BOB_DEFAULT_PICTURE.PNG" .
methods to_html
redefinition .
protected section.
private section.
endclass.
class zcl_html_image implementation.
method constructor.
super->constructor(
exporting
iv_tag = 'img'
iv_id = iv_id
iv_class = iv_class ).
if iv_src is not initial .
append value #( name = 'src' value = iv_src ) to attributes .
endif.
if iv_alt is not initial .
append value #( name = 'alt' value = iv_alt ) to attributes .
endif.
endmethod.
method embedd_mime.
data: lv_image_base64 type string .
data(lo_mime_api) = cl_mime_repository_api=>get_api( ).
lo_mime_api->get(
exporting
i_url = iv_mime_path
importing
e_content = data(lv_content_x)
exceptions
parameter_missing = 1
error_occured = 2
not_found = 3
permission_failure = 4 ).
if sy-subrc <> 0.
return .
endif.
call function 'SCMS_BASE64_ENCODE_STR'
exporting
input = lv_content_x
importing
output = lv_image_base64.
lv_image_base64 = 'data:image/png;base64,' && lv_image_base64 .
read table attributes with key name = 'src' assigning field-symbol(<att>).
if sy-subrc = 0 .
<att>-value = lv_image_base64 .
else.
append value #( name = 'src' value = lv_image_base64 ) to attributes .
endif.
endmethod.
method set_height.
append value #( name = 'height' value = iv_value ) to attributes .
endmethod.
method set_width.
append value #( name = 'width' value = iv_value ) to attributes .
endmethod.
method to_html.
rv_value = |<{ tag }| &&
reduce string( init att_html type string
for <attrib> in me->attributes
next att_html = att_html &&
| { <attrib>-name }="{ <attrib>-value }"| ) &&
|/>\n|.
endmethod.
endclass.
Class ZCL_HTML_LINE_BREAK
Add line break tag <br>
class zcl_html_line_break definition
public
inheriting from zcl_html_element
final
create public .
public section.
methods constructor .
methods to_html
redefinition .
protected section.
private section.
endclass.
class zcl_html_line_break implementation.
method constructor.
super->constructor( exporting iv_tag = 'br' ).
endmethod.
method to_html.
rv_value = |<{ tag }>\n| .
endmethod.
endclass.
Class ZCL_HTML_PARAGRAPH
Add paragraph to html. Support to add long text as paragraph.
class zcl_html_paragraph definition
public
inheriting from zcl_html_element
final
create public .
public section.
methods constructor
importing
!iv_id type string optional
!iv_value type string optional
!iv_class type string optional .
methods set_long_text
importing
!iv_id type csequence default 'ST'
!iv_name type csequence
!iv_langu type thead-tdspras default sy-langu
!iv_object type csequence default 'TEXT' .
protected section.
private section.
methods read_so10_to_str
importing
!iv_id type thead-tdid default 'ST'
!iv_name type thead-tdname
!iv_langu type thead-tdspras default sy-langu
!iv_object type thead-tdobject default 'TEXT'
returning
value(rv_text) type string .
endclass.
class zcl_html_paragraph implementation.
method constructor.
super->constructor( exporting
iv_tag = 'p'
iv_id = iv_id
iv_value = iv_value
iv_class = iv_class ).
endmethod.
method read_so10_to_str.
data : lt_lines type idmx_di_t_tline .
clear rv_text .
call function 'READ_TEXT'
exporting
id = iv_id
language = iv_langu
name = iv_name
object = iv_object
tables
lines = lt_lines
exceptions
id = 1
language = 2
name = 3
not_found = 4
object = 5
reference_check = 6
wrong_access_to_archive = 7.
if sy-subrc <> 0 .
return .
endif.
call function 'IDMX_DI_TLINE_INTO_STRING'
exporting
it_tline = lt_lines
importing
ev_text_string = rv_text.
endmethod.
method set_long_text.
data(lv_text) = read_so10_to_str(
exporting
iv_id = conv #( iv_id )
iv_name = conv #( iv_name )
iv_langu = iv_langu
iv_object = conv #( iv_object ) ).
set_value( iv_value = lv_text ) .
endmethod.
endclass.
Class ZCL_HTML_TABLE
class zcl_html_table definition
public
inheriting from zcl_html_element
final
create public .
public section.
methods constructor
importing
!iv_id type string optional
!iv_class type string optional .
methods set_table_data
importing
!it_table type table
!iv_visible_columns type csequence optional .
methods set_struct_data
importing
!is_structure type data
!iv_visible_fields type csequence .
protected section.
private section.
data mt_visible_column type stringtab .
methods is_field_visible
importing
!iv_column type string
returning
value(rv_is_visible) type abap_bool .
methods get_text_by_rollname
importing
!iv_rollname type csequence
returning
value(rv_text) type string .
methods set_visible_fields
importing
!iv_visible_fields type csequence .
endclass.
class zcl_html_table implementation.
method constructor.
super->constructor( exporting iv_tag = 'table' iv_id = iv_id iv_class = iv_class ).
endmethod.
method get_text_by_rollname.
select single scrtext_m
from dd04t into rv_text
where rollname = iv_rollname
and ddlanguage = sy-langu.
endmethod.
method is_field_visible.
if mt_visible_column is initial.
rv_is_visible = abap_true.
else.
read table mt_visible_column transporting no fields with table key table_line = iv_column.
if sy-subrc = 0.
rv_is_visible = abap_true.
else.
rv_is_visible = abap_false.
endif.
endif.
endmethod.
method set_struct_data.
data: lr_struct_desc type ref to cl_abap_structdescr,
lt_comp type abap_compdescr_tab,
lt_comp_temp type abap_compdescr_tab,
lr_type_descr type ref to cl_abap_typedescr,
lt_component type tt_component,
lr_data type ref to data,
lv_label type string,
lv_rollname type string,
lr_table_descr type ref to cl_abap_tabledescr,
ls_x030l type x030l,
lv_tabname type tabname.
field-symbols: <lt_table> type any table.
set_visible_fields( iv_visible_fields = iv_visible_fields ).
lr_struct_desc ?= cl_abap_typedescr=>describe_by_data( is_structure ).
lt_comp = lr_struct_desc->components.
loop at lt_comp assigning field-symbol(<ls_comp>).
lr_type_descr = lr_struct_desc->get_component_type( p_name = <ls_comp>-name ).
try.
lr_struct_desc ?= lr_type_descr.
lt_comp_temp = lr_struct_desc->components.
delete lt_comp.
append lines of lt_comp_temp to lt_comp.
catch cx_sy_move_cast_error.
append value #( name = <ls_comp>-name type = lr_type_descr ) to lt_component .
endtry.
endloop.
assign is_structure to field-symbol(<ls_structure>).
loop at lt_component assigning field-symbol(<ls_component>).
if is_field_visible( <ls_component>-name ) = abap_false.
continue.
endif.
if <ls_component>-type->is_ddic_type( ) = 'X'.
lv_rollname = <ls_component>-type->get_relative_name( ).
lv_label = get_text_by_rollname( lv_rollname ).
else.
lv_rollname = lv_label = <ls_component>-name.
endif.
data(row) = new zcl_html_element( iv_tag = 'tr' ) .
me->add_child( row ) .
row->add_child( new zcl_html_element( iv_tag = 'th' iv_value = lv_label ) ) .
assign component <ls_component>-name of structure <ls_structure> to field-symbol(<lv_value>).
try.
lr_table_descr ?= <ls_component>-type.
ls_x030l = lr_table_descr->get_ddic_header( ).
lv_tabname = ls_x030l-tabname.
clear: lv_label.
create data lr_data like table of lv_tabname.
assign lr_data->* to <lt_table>.
<lt_table> = <lv_value>.
loop at <lt_table> assigning <lv_value>.
if lv_label <> ''.
concatenate lv_label <lv_value> into lv_label separated by ','.
else.
lv_label = <lv_value>.
endif.
endloop.
catch cx_sy_move_cast_error.
if <lv_value> is assigned.
lv_label = <lv_value>.
endif.
endtry.
row->add_child( new zcl_html_element( iv_tag = 'td' iv_value = lv_label ) ) .
endloop.
endmethod.
method set_table_data.
data: lr_desc type ref to cl_abap_tabledescr,
lr_struct_desc type ref to cl_abap_structdescr,
lt_comp_temp type abap_compdescr_tab,
lr_type_descr type ref to cl_abap_typedescr,
lt_component type tt_component,
lr_table_descr type ref to cl_abap_tabledescr,
lv_label type string.
set_visible_fields( iv_visible_fields = iv_visible_columns ).
lr_desc ?= cl_abap_typedescr=>describe_by_data( it_table ).
lr_struct_desc ?= lr_desc->get_table_line_type( ).
data(lt_comp) = lr_struct_desc->components.
loop at lt_comp assigning field-symbol(<ls_comp>).
lr_type_descr = lr_struct_desc->get_component_type( p_name = <ls_comp>-name ).
try.
lr_struct_desc ?= lr_type_descr.
lt_comp_temp = lr_struct_desc->components.
delete lt_comp.
append lines of lt_comp_temp to lt_comp.
catch cx_sy_move_cast_error.
append value #( name = <ls_comp>-name type = lr_type_descr ) to lt_component .
endtry.
endloop.
"table header
loop at lt_component assigning field-symbol(<ls_component>).
if is_field_visible( <ls_component>-name ) = abap_false.
continue.
endif.
if <ls_component>-type->is_ddic_type( ) = abap_true .
data(lv_rollname) = <ls_component>-type->get_relative_name( ).
lv_label = get_text_by_rollname( lv_rollname ).
else.
lv_label = <ls_component>-name.
endif.
me->add_child( new zcl_html_element( iv_tag = 'th' iv_value = lv_label ) ) .
endloop.
loop at it_table assigning field-symbol(<ls_line>).
"table rows
data(row) = new zcl_html_element( iv_tag = 'tr' ) .
me->add_child( row ) .
loop at lt_component assigning <ls_component>.
if is_field_visible( <ls_component>-name ) = abap_false.
continue.
endif.
"table cell
clear lv_label .
try.
lr_table_descr ?= <ls_component>-type.
catch cx_sy_move_cast_error.
assign component <ls_component>-name of structure <ls_line> to field-symbol(<lv_value>).
if <lv_value> is assigned.
lv_label = <lv_value>.
endif.
endtry.
row->add_child( new zcl_html_element( iv_tag = 'td' iv_value = lv_label ) ) .
endloop.
endloop.
endmethod.
method set_visible_fields.
if iv_visible_fields is not initial .
data(lv_visible_columns) = iv_visible_fields .
translate lv_visible_columns to upper case .
split lv_visible_columns at ',' into table mt_visible_column.
endif.
endmethod.
endclass.
Demo Program
report z_demo_html.
data html type ref to zcl_html_document .
*&---------------------------------------------------------------------*
*& start-of-selection
*&---------------------------------------------------------------------*
start-of-selection .
select single from ekko fields *
where ebeln = '4500000014' into @data(po_header) .
select from ekko fields *
where ebeln = '4500000014' into table @data(po_head_line) .
select from ekpo fields *
where ebeln = '4500000014' into table @data(polines) .
perform prepare_html .
perform send_email.
*&---------------------------------------------------------------------*
*& send email
*&---------------------------------------------------------------------*
form send_email .
try.
data(send_request) = cl_bcs=>create_persistent( ).
data(text_html) = cl_document_bcs=>string_to_soli( ip_string = html->to_html( ) ).
data(document) = cl_document_bcs=>create_document(
i_type = 'HTM'
i_text = text_html
i_subject = 'Demo HTML Email' ).
send_request->set_document( document ).
data(recipient) = cl_cam_address_bcs=>create_internet_address(
'replacewith@email.com' ).
send_request->add_recipient(
i_recipient = recipient
i_express = abap_true ) .
send_request->send( ).
commit work.
catch cx_bcs into data(bcs_exception).
endtry .
endform.
*&---------------------------------------------------------------------*
*& PREPARE HTML
*&---------------------------------------------------------------------*
form prepare_html .
html = new zcl_html_document( ) .
data lv_css type string .
lv_css = | p \{ | &&
| font-family: Garamond, Helvetica, sans-serif; | &&
| font-weight: bold; | &&
|\} | &&
|table \{ | &&
| border: 1px solid #000000; | &&
| border-collapse: collapse; | &&
| font-family: Garamond, Helvetica, sans-serif; | &&
|\} | &&
|table td, | &&
|table th \{ | &&
| border: 1px solid #000000; | &&
| padding: 10px 10px; | &&
|\} | &&
| | &&
|table tr:nth-child(even) \{ | &&
| background: #D0E4F5; | &&
|\} | &&
| | &&
|th \{ | &&
| background-color: #7EA8F8 | &&
|\} | .
data(html_style) = new zcl_html_element( iv_tag = 'style' iv_value = lv_css ).
html->head->add_child( html_style ) .
data(html_paragraph) = new zcl_html_paragraph( ) .
html_paragraph->set_long_text(
exporting
iv_id = 'F02'
iv_name = po_header-ebeln
iv_object = 'EKKO' ).
html->body->add_child( html_paragraph ) .
data(html_po_head) = new zcl_html_table( ).
html_po_head->set_struct_data(
is_structure = po_header
iv_visible_fields = 'ebeln,lifnr' ) .
html->body->add_child( html_po_head ) .
html->line_break( ) .
data(table1) = new zcl_html_table( ).
table1->set_table_data(
it_table = po_head_line
iv_visible_columns = 'ebeln,lifnr' ) .
html->body->add_child( table1 ) .
html->line_break( ) .
data(table) = new zcl_html_table( ).
table->set_table_data(
it_table = polines
iv_visible_columns = 'ebeln,ebelp,txz01,menge,meins' ) .
html->body->add_child( table ) .
html->line_break( ) .
data(img) = new zcl_html_image( iv_src = 'https://www.seekpng.com/png/full/382-3821698_hbo-logo-white-png-download-sap-logo-white.png'
iv_alt = 'SAP Logo' ).
img->set_width( '150' ).
html->body->add_child( img ) .
html->line_break( ) .
data(img_mime) = new zcl_html_image( iv_alt = 'mime embedded image' ) .
img_mime->embedd_mime( iv_mime_path = '/SAP/PUBLIC/BOBF/BOB/bob_default_picture.png' ) .
img_mime->set_width( '150' ).
html->body->add_child( img_mime ) .
"cl_demo_output=>write_html( html->to_html( ) ) .
"cl_demo_output=>display( ) .
endform .
CSS
You would have noticed I have added css within STYLE tag to format the email. I think great way to format instead of formatting each element individually. You could ask your client for example to provide you with css while you focus on getting content for email.
With below css email appearance has improved.
p {
font-family: Garamond, Helvetica, sans-serif;
font-weight: bold;
}
table {
border: 1px solid #000000;
border-collapse: collapse;
font-family: Garamond, Helvetica, sans-serif;
}
table td,
table th {
border: 1px solid #000000;
padding: 10px 10px;
}
table tr:nth-child(even) {
background: #D0E4F5;
}
th {
background-color: #7EA8F8;
}
Very nice, request you to advice me how to pass the long url in html tags – url having more than 500 chars,
thanks in advance.
Hello! Thank you for your contribution! It’s me or method “to_html” is not compiling? Cheers!
to_html method has an error.
rv_value = |<{ tag }| < &&
"<" is not a permitted operator in this position of the calculation expression."
Update to fix to_html:
Simply remove the second ‘<' from rv_value = |<{ tag }| < &&
i.e. it should be: rv_value = |<{ tag }| &&