From 9efdae315d91aa9535978fd79448b27c9099b308 Mon Sep 17 00:00:00 2001 From: Marc Bernard <59966492+mbtools@users.noreply.github.com> Date: Tue, 24 Nov 2020 02:11:53 -0500 Subject: [PATCH] Dialogs: Enhance HTML form with table control (#4230) * Dialogs: Enhance HTML form with table control - Adds option to maintain data using a table control - Extends UTs with several cases that were include in #4172 - Table control to be used by "repo settings" #4171 * Border * Lint Co-authored-by: Lars Hvam --- src/ui/zabapgit_css_common.w3mi.data.css | 21 + src/ui/zcl_abapgit_html_form.clas.abap | 440 ++++++++++++++---- ...cl_abapgit_html_form.clas.testclasses.abap | 146 +++++- 3 files changed, 508 insertions(+), 99 deletions(-) diff --git a/src/ui/zabapgit_css_common.w3mi.data.css b/src/ui/zabapgit_css_common.w3mi.data.css index cec9e4748..69e5a465d 100644 --- a/src/ui/zabapgit_css_common.w3mi.data.css +++ b/src/ui/zabapgit_css_common.w3mi.data.css @@ -67,6 +67,7 @@ span.separator { .nodisplay { display: none } .m-em5-sides { margin-left: 0.5em; margin-right: 0.5em } .w600px { width: 600px } +.w1000px { width: 1000px } .wmax600px { max-width: 600px } .auto-center { /* use with max-width */ width: 100%; @@ -1109,6 +1110,7 @@ settings_tab tr:first-child td { border-top: 0px; } width: 100%; box-sizing: border-box; padding: 10px; + font-family: Arial,Helvetica,sans-serif; } .dialog .radio-container input[type="radio"] { visibility: hidden; @@ -1128,6 +1130,25 @@ settings_tab tr:first-child td { border-top: 0px; } .dialog .radio-container input[type="radio"]:checked + label { border: 1px solid transparent; } +.dialog table { + width: 100%; +} +.dialog table thead td { + font-size: 14px; + height: 2.5em; + background-color: #ddd; + border: 1px solid #ddd; + padding: 0px 10px; +} +.dialog table tbody td { + border: 1px solid #ccc; + background-color: white; +} +.dialog table td input { + margin: 0px 2px; + border: 0px; + background: none; +} .dialog li.with-command div.input-container { display: table-cell; width: 100%; diff --git a/src/ui/zcl_abapgit_html_form.clas.abap b/src/ui/zcl_abapgit_html_form.clas.abap index 02724f7c5..7102c7dd7 100644 --- a/src/ui/zcl_abapgit_html_form.clas.abap +++ b/src/ui/zcl_abapgit_html_form.clas.abap @@ -5,6 +5,8 @@ CLASS zcl_abapgit_html_form DEFINITION PUBLIC SECTION. + CONSTANTS c_rows TYPE string VALUE 'rows' ##NO_TEXT. + CLASS-METHODS create IMPORTING !iv_form_id TYPE csequence OPTIONAL @@ -83,6 +85,19 @@ CLASS zcl_abapgit_html_form DEFINITION !iv_value TYPE csequence RETURNING VALUE(ro_self) TYPE REF TO zcl_abapgit_html_form. + METHODS table + IMPORTING + !iv_label TYPE csequence + !iv_name TYPE csequence + !iv_hint TYPE csequence OPTIONAL + RETURNING + VALUE(ro_self) TYPE REF TO zcl_abapgit_html_form. + METHODS column + IMPORTING + !iv_label TYPE csequence + !iv_width TYPE csequence OPTIONAL + RETURNING + VALUE(ro_self) TYPE REF TO zcl_abapgit_html_form. METHODS start_group IMPORTING !iv_label TYPE csequence @@ -143,6 +158,15 @@ CLASS zcl_abapgit_html_form DEFINITION as_a TYPE abap_bool, * onclick ??? END OF ty_command. + TYPES: + BEGIN OF ty_attr, + value TYPE string, + error TYPE string, + hint TYPE string, + readonly TYPE string, + placeholder TYPE string, + required TYPE string, + END OF ty_attr. CONSTANTS: BEGIN OF c_field_type, @@ -152,6 +176,7 @@ CLASS zcl_abapgit_html_form DEFINITION field_group TYPE i VALUE 4, number TYPE i VALUE 5, textarea TYPE i VALUE 6, + table TYPE i VALUE 7, END OF c_field_type. DATA: mt_fields TYPE STANDARD TABLE OF ty_field @@ -167,6 +192,32 @@ CLASS zcl_abapgit_html_form DEFINITION !io_values TYPE REF TO zcl_abapgit_string_map !io_validation_log TYPE REF TO zcl_abapgit_string_map !is_field TYPE ty_field. + METHODS render_field_text + IMPORTING + !ii_html TYPE REF TO zif_abapgit_html + !is_field TYPE ty_field + !is_attr TYPE ty_attr. + METHODS render_field_textarea + IMPORTING + !ii_html TYPE REF TO zif_abapgit_html + !is_field TYPE ty_field + !is_attr TYPE ty_attr. + METHODS render_field_checkbox + IMPORTING + !ii_html TYPE REF TO zif_abapgit_html + !is_field TYPE ty_field + !is_attr TYPE ty_attr. + METHODS render_field_radio + IMPORTING + !ii_html TYPE REF TO zif_abapgit_html + !is_field TYPE ty_field + !is_attr TYPE ty_attr. + METHODS render_field_table + IMPORTING + !ii_html TYPE REF TO zif_abapgit_html + !is_field TYPE ty_field + !is_attr TYPE ty_attr + !io_values TYPE REF TO zcl_abapgit_string_map. METHODS render_command IMPORTING !ii_html TYPE REF TO zif_abapgit_html @@ -194,6 +245,29 @@ CLASS zcl_abapgit_html_form IMPLEMENTATION. ENDMETHOD. + METHOD column. + + FIELD-SYMBOLS LIKE LINE OF mt_fields. + DATA ls_column LIKE LINE OF -subitems. + DATA lv_size TYPE i. + + lv_size = lines( mt_fields ). + ASSERT lv_size > 0. " Exception ? Maybe add zcx_no_check ? + + READ TABLE mt_fields INDEX lv_size ASSIGNING . + ASSERT sy-subrc = 0. + ASSERT -type = c_field_type-table. + + ls_column-label = iv_label. + ls_column-value = iv_width. + + APPEND ls_column TO -subitems. + + ro_self = me. + + ENDMETHOD. + + METHOD command. DATA ls_cmd LIKE LINE OF mt_commands. @@ -231,6 +305,8 @@ CLASS zcl_abapgit_html_form IMPLEMENTATION. METHOD normalize_form_data. DATA lv_value TYPE string. + DATA lv_rows TYPE i. + DATA lv_row TYPE i. FIELD-SYMBOLS LIKE LINE OF mt_fields. CREATE OBJECT ro_form_data. @@ -248,12 +324,21 @@ CLASS zcl_abapgit_html_form IMPLEMENTATION. iv_key = -name iv_val = to_upper( lv_value ) ). ELSEIF -type = c_field_type-number. - IF lv_value NA '0123456789- '. - - ENDIF. + " Numeric value is checked in validation ro_form_data->set( iv_key = -name - iv_val = lv_value ). + iv_val = condense( val = lv_value del = ` ` ) ). + ELSEIF -type = c_field_type-table. + lv_rows = io_form_data->get( |{ -name }-{ c_rows }| ). + DO lv_rows TIMES. + lv_row = sy-index. + DO lines( -subitems ) TIMES. + lv_value = io_form_data->get( |{ -name }-{ lv_row }-{ sy-index }| ). + ro_form_data->set( + iv_key = |{ -name }-{ lv_row }-{ sy-index }| + iv_val = lv_value ). + ENDDO. + ENDDO. ELSE. ro_form_data->set( iv_key = -name @@ -439,128 +524,96 @@ CLASS zcl_abapgit_html_form IMPLEMENTATION. METHOD render_field. - DATA lv_opt_id TYPE string. - DATA lv_error TYPE string. - DATA lv_value TYPE string. - DATA lv_checked TYPE string. - DATA lv_item_class TYPE string. - DATA lv_hint TYPE string. - DATA lv_required TYPE string. - DATA lv_attr TYPE string. - DATA lv_type TYPE string. - DATA lv_rows TYPE i. - FIELD-SYMBOLS LIKE LINE OF is_field-subitems. + DATA: + ls_attr TYPE ty_attr, + lv_item_class TYPE string. + + " Get value and validation error + ls_attr-value = escape( val = io_values->get( is_field-name ) + format = cl_abap_format=>e_html_attr ). - " Get value and validation error from maps - lv_value = io_values->get( is_field-name ). IF io_validation_log IS BOUND. - lv_error = io_validation_log->get( is_field-name ). + ls_attr-error = io_validation_log->get( is_field-name ). + IF ls_attr-error IS NOT INITIAL. + ls_attr-error = escape( val = ls_attr-error + format = cl_abap_format=>e_html_text ). + ls_attr-error = |{ ls_attr-error }|. + ENDIF. + ENDIF. + + " Prepare field attributes + IF is_field-required = abap_true. + ls_attr-required = ' *'. + ENDIF. + + IF is_field-hint IS NOT INITIAL. + ls_attr-hint = escape( val = is_field-hint + format = cl_abap_format=>e_html_attr ). + ls_attr-hint = | title="{ ls_attr-hint }"|. + ENDIF. + + IF is_field-placeholder IS NOT INITIAL. + ls_attr-placeholder = escape( val = is_field-placeholder + format = cl_abap_format=>e_html_attr ). + ls_attr-placeholder = | placeholder="{ ls_attr-placeholder }"|. + ENDIF. + + IF is_field-readonly = abap_true. + ls_attr-readonly = ' readonly'. ENDIF. " Prepare item class lv_item_class = is_field-item_class. - IF lv_error IS NOT INITIAL. + IF ls_attr-error IS NOT INITIAL. lv_item_class = condense( lv_item_class && ' error' ). ENDIF. + IF is_field-type = c_field_type-text AND is_field-max BETWEEN 1 AND 20. + " Reduced width for short fields + lv_item_class = lv_item_class && ' w40'. + ENDIF. IF lv_item_class IS NOT INITIAL. lv_item_class = | class="{ lv_item_class }"|. ENDIF. - IF is_field-required = abap_true. - lv_required = ' *'. - ENDIF. - - IF is_field-hint IS NOT INITIAL. - lv_hint = | title="{ is_field-hint }"|. - ENDIF. - - IF is_field-readonly = abap_true. - lv_attr = lv_attr && ' readonly'. - ENDIF. - - IF is_field-placeholder IS NOT INITIAL. - lv_attr = lv_attr && | placeholder="{ is_field-placeholder }"|. - ENDIF. - " Render field ii_html->add( || ). CASE is_field-type. WHEN c_field_type-text OR c_field_type-number. - ii_html->add( || ). - IF lv_error IS NOT INITIAL. - ii_html->add( |{ lv_error }| ). - ENDIF. - - IF is_field-side_action IS NOT INITIAL. - ii_html->add( '
' ). " Ugly :( - ENDIF. - - IF is_field-type = c_field_type-number. - lv_type = 'number'. - ELSEIF is_field-password = abap_true. - lv_type = 'password'. - ELSE. - lv_type = 'text'. - ENDIF. - - ii_html->add( || ). - - IF is_field-side_action IS NOT INITIAL. - ii_html->add( '
' ). - ii_html->add( '
' ). - ii_html->add( || ). - ii_html->add( '
' ). - ENDIF. + render_field_text( + ii_html = ii_html + is_field = is_field + is_attr = ls_attr ). WHEN c_field_type-textarea. - ii_html->add( || ). - IF lv_error IS NOT INITIAL. - ii_html->add( |{ lv_error }| ). - ENDIF. - - lv_rows = lines( zcl_abapgit_convert=>split_string( lv_value ) ) + 1. - - ii_html->add( || ). + render_field_textarea( + ii_html = ii_html + is_field = is_field + is_attr = ls_attr ). WHEN c_field_type-checkbox. - IF lv_error IS NOT INITIAL. - ii_html->add( |{ lv_error }| ). - ENDIF. - IF lv_value = abap_true OR lv_value = 'on'. " boolc return ` ` which is not initial -> bug after 1st validation - lv_checked = ' checked'. - ENDIF. - ii_html->add( || ). - ii_html->add( || ). + render_field_checkbox( + ii_html = ii_html + is_field = is_field + is_attr = ls_attr ). WHEN c_field_type-radio. - ii_html->add( |{ is_field-label }| ). - IF lv_error IS NOT INITIAL. - ii_html->add( |{ lv_error }| ). - ENDIF. - ii_html->add( |
| ). + render_field_radio( + ii_html = ii_html + is_field = is_field + is_attr = ls_attr ). - LOOP AT is_field-subitems ASSIGNING . - CLEAR lv_checked. - IF lv_value = -value OR ( lv_value IS INITIAL AND -value = is_field-default_value ). - lv_checked = ' checked'. - ENDIF. - lv_opt_id = |{ is_field-name }{ sy-tabix }|. - ii_html->add( || ). - ii_html->add( || ). - ENDLOOP. + WHEN c_field_type-table. - ii_html->add( '
' ). + render_field_table( + ii_html = ii_html + is_field = is_field + is_attr = ls_attr + io_values = io_values ). WHEN OTHERS. ASSERT 1 = 0. @@ -571,6 +624,181 @@ CLASS zcl_abapgit_html_form IMPLEMENTATION. ENDMETHOD. + METHOD render_field_checkbox. + + DATA lv_checked TYPE string. + + IF is_attr-error IS NOT INITIAL. + ii_html->add( is_attr-error ). + ENDIF. + + IF is_attr-value = abap_true OR is_attr-value = 'on'. + " boolc return ` ` which is not initial -> bug after 1st validation + lv_checked = ' checked'. + ENDIF. + + ii_html->add( || ). + ii_html->add( || ). + + ENDMETHOD. + + + METHOD render_field_radio. + + DATA: + lv_value TYPE string, + lv_checked TYPE string, + lv_opt_id TYPE string, + lv_opt_value TYPE string. + + FIELD-SYMBOLS LIKE LINE OF is_field-subitems. + + ii_html->add( |{ is_field-label }| ). + + IF is_attr-error IS NOT INITIAL. + ii_html->add( is_attr-error ). + ENDIF. + + ii_html->add( |
| ). + + LOOP AT is_field-subitems ASSIGNING . + lv_opt_value = escape( val = -value + format = cl_abap_format=>e_html_attr ). + + CLEAR lv_checked. + IF is_attr-value = lv_opt_value OR ( is_attr-value IS INITIAL AND lv_opt_value = is_field-default_value ). + lv_checked = ' checked'. + ENDIF. + + lv_opt_id = |{ is_field-name }{ sy-tabix }|. + ii_html->add( || ). + ii_html->add( || ). + ENDLOOP. + + ii_html->add( '
' ). + + ENDMETHOD. + + + METHOD render_field_table. + + DATA: + lv_value TYPE string, + lv_rows TYPE i, + lv_cell_id TYPE string, + lv_opt_value TYPE string. + + FIELD-SYMBOLS LIKE LINE OF is_field-subitems. + + ii_html->add( || ). + + IF is_attr-error IS NOT INITIAL. + ii_html->add( is_attr-error ). + ENDIF. + + ii_html->add( || ). + + ii_html->add( || ). + ii_html->add( || ). + LOOP AT is_field-subitems ASSIGNING . + CLEAR lv_value. + IF -value IS NOT INITIAL. + lv_value = escape( val = -value + format = cl_abap_format=>e_html_attr ). + lv_value = | width="{ lv_value }"|. + ENDIF. + ii_html->add( |{ -label }| ). + ENDLOOP. + ii_html->add( || ). + ii_html->add( || ). + + lv_rows = io_values->get( |{ is_field-name }-{ c_rows }| ). + + ii_html->add( || ). + DO lv_rows TIMES. + lv_rows = sy-index. + ii_html->add( || ). + LOOP AT is_field-subitems ASSIGNING . + lv_cell_id = |{ is_field-name }-{ lv_rows }-{ sy-tabix }|. + lv_value = escape( val = io_values->get( lv_cell_id ) + format = cl_abap_format=>e_html_attr ). + ii_html->add( || ). + ENDLOOP. + ii_html->add( || ). + ENDDO. + ii_html->add( || ). + + ii_html->add( |
| ). + + " Hidden field with number of rows to simplify getting values from form + lv_value = |{ is_field-name }-{ c_rows }|. + ii_html->add( || ). + + ENDMETHOD. + + + METHOD render_field_text. + + DATA lv_type TYPE string. + + ii_html->add( || ). + + IF is_attr-error IS NOT INITIAL. + ii_html->add( is_attr-error ). + ENDIF. + + IF is_field-side_action IS NOT INITIAL. + ii_html->add( '
' ). " Ugly :( + ENDIF. + + IF is_field-type = c_field_type-number. + lv_type = 'number'. + ELSEIF is_field-password = abap_true. + lv_type = 'password'. + ELSE. + lv_type = 'text'. + ENDIF. + + ii_html->add( || ). + + IF is_field-side_action IS NOT INITIAL. + ii_html->add( '
' ). + ii_html->add( '
' ). + ii_html->add( || ). + ii_html->add( '
' ). + ENDIF. + + ENDMETHOD. + + + METHOD render_field_textarea. + + DATA lv_rows TYPE i. + + ii_html->add( || ). + + IF is_attr-error IS NOT INITIAL. + ii_html->add( is_attr-error ). + ENDIF. + + lv_rows = lines( zcl_abapgit_convert=>split_string( is_attr-value ) ) + 1. " one new row + + ii_html->add( || ). + + ENDMETHOD. + + METHOD start_group. DATA ls_field LIKE LINE OF mt_fields. @@ -587,6 +815,22 @@ CLASS zcl_abapgit_html_form IMPLEMENTATION. ENDMETHOD. + METHOD table. + + DATA ls_field LIKE LINE OF mt_fields. + + ls_field-type = c_field_type-table. + ls_field-name = iv_name. + ls_field-label = iv_label. + ls_field-hint = iv_hint. + + APPEND ls_field TO mt_fields. + + ro_self = me. + + ENDMETHOD. + + METHOD text. DATA ls_field LIKE LINE OF mt_fields. @@ -671,7 +915,7 @@ CLASS zcl_abapgit_html_form IMPLEMENTATION. CATCH cx_root. ro_validation_log->set( iv_key = -name - iv_val = |{ -label } is not a number| ). + iv_val = |{ -label } is not numeric| ). CONTINUE. ENDTRY. IF -min <> cl_abap_math=>min_int4 AND lv_number < -min. diff --git a/src/ui/zcl_abapgit_html_form.clas.testclasses.abap b/src/ui/zcl_abapgit_html_form.clas.testclasses.abap index 05320f837..31539078e 100644 --- a/src/ui/zcl_abapgit_html_form.clas.testclasses.abap +++ b/src/ui/zcl_abapgit_html_form.clas.testclasses.abap @@ -57,6 +57,95 @@ CLASS ltcl_test_form IMPLEMENTATION. act = lo_log->size( ) exp = 0 ). + " New form + lo_cut = zcl_abapgit_html_form=>create( ). + + lo_cut->text( + iv_name = 'field3' + iv_min = 3 + iv_max = 10 + iv_label = 'Field name 3' ). + + lo_form_data->set( + iv_key = 'field3' + iv_val = 'xy' ). + lo_log = lo_cut->validate_required_fields( lo_form_data ). + cl_abap_unit_assert=>assert_equals( + act = lo_log->size( ) + exp = 1 ). + cl_abap_unit_assert=>assert_char_cp( + act = lo_log->get( 'field3' ) + exp = '*must not be shorter*' ). + + lo_form_data->set( + iv_key = 'field3' + iv_val = '01234567890123' ). + lo_log = lo_cut->validate_required_fields( lo_form_data ). + cl_abap_unit_assert=>assert_equals( + act = lo_log->size( ) + exp = 1 ). + cl_abap_unit_assert=>assert_char_cp( + act = lo_log->get( 'field3' ) + exp = '*must not be longer*' ). + + lo_form_data->set( + iv_key = 'field3' + iv_val = 'xyz!' ). + lo_log = lo_cut->validate_required_fields( lo_form_data ). + cl_abap_unit_assert=>assert_equals( + act = lo_log->size( ) + exp = 0 ). + + " New form + lo_cut = zcl_abapgit_html_form=>create( ). + + lo_cut->number( + iv_name = 'field4' + iv_min = 100 + iv_max = 200 + iv_label = 'Field name 4' ). + + lo_form_data->set( + iv_key = 'field4' + iv_val = '123-456' ). + lo_log = lo_cut->validate_required_fields( lo_form_data ). + cl_abap_unit_assert=>assert_equals( + act = lo_log->size( ) + exp = 1 ). + cl_abap_unit_assert=>assert_char_cp( + act = lo_log->get( 'field4' ) + exp = '*is not numeric*' ). + + lo_form_data->set( + iv_key = 'field4' + iv_val = '50' ). + lo_log = lo_cut->validate_required_fields( lo_form_data ). + cl_abap_unit_assert=>assert_equals( + act = lo_log->size( ) + exp = 1 ). + cl_abap_unit_assert=>assert_char_cp( + act = lo_log->get( 'field4' ) + exp = '*must not be lower*' ). + + lo_form_data->set( + iv_key = 'field4' + iv_val = '250' ). + lo_log = lo_cut->validate_required_fields( lo_form_data ). + cl_abap_unit_assert=>assert_equals( + act = lo_log->size( ) + exp = 1 ). + cl_abap_unit_assert=>assert_char_cp( + act = lo_log->get( 'field4' ) + exp = '*must not be higher*' ). + + lo_form_data->set( + iv_key = 'field4' + iv_val = '150' ). + lo_log = lo_cut->validate_required_fields( lo_form_data ). + cl_abap_unit_assert=>assert_equals( + act = lo_log->size( ) + exp = 0 ). + ENDMETHOD. METHOD normalize. @@ -86,6 +175,17 @@ CLASS ltcl_test_form IMPLEMENTATION. lo_cut->checkbox( iv_name = 'chk2' iv_label = 'Checkbox2' ). + lo_cut->number( + iv_name = 'num1' + iv_label = 'Number 1' ). + lo_cut->table( + iv_name = 'tab1' + iv_label = 'Table 1' ). + lo_cut->column( iv_label = 'Column 1' ). + lo_cut->column( iv_label = 'Column 2' ). + lo_cut->number( + iv_name = |tab1-{ zcl_abapgit_html_form=>c_rows }| + iv_label = 'Number of Rows' ). " simulate hidden form field lo_form_data->set( iv_key = 'field1' @@ -93,7 +193,7 @@ CLASS ltcl_test_form IMPLEMENTATION. lo_form_data->set( iv_key = 'field2' iv_val = 'val2' ). - " Intentinally field3 is not specificed + " Intentionally field3 is not specificed lo_form_data->set( iv_key = 'chk1' iv_val = '' ). @@ -104,6 +204,27 @@ CLASS ltcl_test_form IMPLEMENTATION. iv_key = 'chk3' iv_val = 'on' ). " Extra field - filtered by normalizing + lo_form_data->set( + iv_key = 'num1' + iv_val = ' 1234' ). + + " Table with 2 rows, 2 columns + lo_form_data->set( + iv_key = |tab1-{ zcl_abapgit_html_form=>c_rows }| + iv_val = '2' ). + lo_form_data->set( + iv_key = |tab1-1-1| + iv_val = 'abc' ). + lo_form_data->set( + iv_key = |tab1-1-2| + iv_val = '123' ). + lo_form_data->set( + iv_key = |tab1-2-1| + iv_val = '' ). + lo_form_data->set( + iv_key = |tab1-2-2| + iv_val = '0' ). + lo_normalized_exp->set( iv_key = 'field1' iv_val = 'val1' ). @@ -119,6 +240,29 @@ CLASS ltcl_test_form IMPLEMENTATION. lo_normalized_exp->set( iv_key = 'chk2' iv_val = 'X' ). + lo_normalized_exp->set( + iv_key = 'chk2' + iv_val = 'X' ). + + lo_normalized_exp->set( + iv_key = 'num1' + iv_val = '1234' ). + + lo_normalized_exp->set( + iv_key = |tab1-{ zcl_abapgit_html_form=>c_rows }| + iv_val = '2' ). + lo_normalized_exp->set( + iv_key = |tab1-1-1| + iv_val = 'abc' ). + lo_normalized_exp->set( + iv_key = |tab1-1-2| + iv_val = '123' ). + lo_normalized_exp->set( + iv_key = |tab1-2-1| + iv_val = '' ). + lo_normalized_exp->set( + iv_key = |tab1-2-2| + iv_val = '0' ). lo_normalized_act = lo_cut->normalize_form_data( lo_form_data ). cl_abap_unit_assert=>assert_equals(