Can CRM Web UI enable a field with Dynamic Mandatory control?
System Environment of Examples (SAP Internal System):
- WEBCUIF 749
- BBPCRM 715
- Use P getter to control the mandatory or not for a field, and to define the P getter to have dynamic mandatory field which will depends on the value of another field on the same page;
- Elaborate the detailed behavior of a field when it is mandatory, how the html was generated;
- Describe a known wrong behavior when define a dynamic mandatory field using customer coding within the current CRM Web UI, and analyze the reason;
- Try to propose suggestion how to bypass the issue given the current CRM Web UI;
- KBA 2013392 – Dynamic Mandatory Field shown as mandatoy when should not be mandatory
- Blog Make a field Dynamically Mandatory – Custom Solution
I guess to define a field as mandatory should be a very common requirement in the world of CRM Web UI. In short, there are two options:
- Via configuration, define the field properties, and turn on or off the Mandatory flag
- Via coding, add logic into the P getter of the interested field
Let’s try a bit advanced requirement: to enable a dynamic mandatory field, the mandatory or not will depend on the value of another field in the same view. Let’s have a fake requirement, when create an Individual Account, if “Academic Title” has value 0002 (which is Professor in my testing system), we would like to have “First Name” field as mandatory. “First Name” field was not mandatory on the initial creation screen.
If we consider the 2 options above, if we would like to use option 1, which is via configuration, we might have to make changes to the configuration xml in the runtime. In theory, this is possible, but compared to the simple fake requirement we are having, it is not worthy to use such complex solution. Alternatively, we’d like to use option 2, that is via coding in P Getter. We only make different coding in step 5 for the above option 2, as following:
I thought it’s done. But when test the result, it turns out to be with the following confused behavior:
Feeling so sad? Let’s try to analyze how such confusion is caused.
After press “Enter” (a roundtrip), for an empty mandatory field, there will be 3 system behaviors
Compared to the previous video, we can see the above 3 system behaviors are inconsistent. After select value 0002 for field “Academic Title”, press Enter. The system behavior 1 and 2a will appear and disappear, which is what we want. But for system behavior 2b and 3, I have to press Enter for the second time to have it correctly shown. The system behavior 3, which is the key logic to forbidden saving when a mandatory field was empty. Given the inconsistent system behavior, the mandatory control cannot be realized correctly. This is Not what we wanted.
KBA 2013392 has described the same issue. And it explains that within the current CRM Web UI frame, only add logic in P getter can’t avoid the above inconsistent system behavior. KBA 2013392 has to comment such behavior as standard limitation. Then are we no way out? No. Let’s start from the comment in KBA 2013392 and analyze the mechanism of mandatory control and see how we can bypass this standard limitation. Here is the comment from KBA 2013392
The method DO_FINISH_INPUT mentioned here is CL_BSP_WD_VIEW_CONTROLLER method DO_FINISH_INPUT. Let’s check the coding there
The “Issue Error” corresponds to the system behavior 3. And “Set Error status” system behavior 2. The following screen shot shows how the “Set Error status” was translated into html presentation. In CL_THTMLB_INPUTFIELD, it takes over the error status passed from method DO_FINISH_INPUT, th-onerror class was added into html. And in CSS definition, the background-color and border-color explains the system behavior 2a and 2b.
When analyzing the Error status, I happen to find the coding which deals with required field status. The following screen shot shows, in CL_THTMLB_INPUTFIELD, system checks the “required” attribute, and set th-ip-sp-md class id into html. This leads to the system behavior of 2a. Regarding the red star, it is processed in CL_THTMLB_LABEL. The “*” was added into the label and CSS forced it to be red.
Then where is this “required” attribute coming from? It is from the two options mentioned at the beginning of this blog. The coding detail as following
Visualize the flow of one roundtrip as following. The error message will be based on last roundtrip’s mandatory fields of the bsp application. Only after it, the P getter will be called. This leads to the inconsistent system behavior.
After analyzing, I feel the mechanism is clear to me. But the initial issue was not solved yet. 😛
I’ve discussed with our Web UI developer. Our developer has included this issue within the to do list. But it might take some time to be solved. Before we get a standard solution, let’s try to see if there is any chance to bypass the standard limitation.
There is a blog in 2013, talking about the same Dynamic Mandatory Field requirement. The author proposed a solution in customer bsp component. This blog will try in the scope of an enhanced bsp component.
Let’s review the visualized flow. Here CL_BSP_WD_VIEW_CONTROLLER method POST_MANDATORY_FIELD added one new mandatory field to the bsp application. This is a public method. If the current flow is to increase one mandatory field, we can call this method before GET_EMPTY_MANDATORY_FIELDS in DO_FINISH_INPUT. But how to decrease mandatory fields? Observe class CL_BSP_WD_VIEW_CONTROLLER attribute MANDATORY_FIELDS, it is a Private attribute. And no public method provided to delete one entry of the mandatory fields. Thus it seems to be a dead end.
Then how about redefine DO_FINISH_INPUT method completely? Or redefine method GET_EMPTY_MANDATORY_FIELDS?
With help from my colleague, we managed to come up with the following coding by redefine method GET_EMPTY_MANDATORY_FIELDS. And the initial issue was solved perfectly.
In the following coding, it is better to create a separate interface or method to capsulate the logic regarding judging the dynamic condition, should or should not undergo extra logic. This can lead to easy reading and extension for future. Here the coding to check a field is empty or not is copied from CL_BSP_WD_VIEW_CONTROLLER method GET_EMPTY_MANDATORY_FIELDS.
METHOD get_empty_mandatory_fields. CALL METHOD super->get_empty_mandatory_fields RECEIVING rt_result = rt_result. DATA: lv_cnode_name TYPE string, lv_last_cnode_name TYPE string, lv_attr_path TYPE string, lv_attr_name TYPE string, lv_attr_value TYPE string, lv_attr_meta TYPE REF TO if_bsp_metadata, lv_exception TYPE REF TO cx_bol_exception, lv_dynamic_mandatory_field TYPE bsp_dlc_binding_string. lv_dynamic_mandatory_field = '//HEADER/STRUCT.FIRSTNAME'. * split binding expression SPLIT lv_dynamic_mandatory_field+2(*) AT '/' INTO lv_cnode_name lv_attr_path. TRANSLATE lv_cnode_name TO LOWER CASE. "#EC SYNTCHAR IF lv_cnode_name NE lv_last_cnode_name. * get cnode instance FIELD-SYMBOLS: <cnode> TYPE lbsp_model_item. READ TABLE me->m_models WITH KEY model_id = lv_cnode_name ASSIGNING <cnode>. ENDIF. IF <cnode> IS ASSIGNED. * was the field input ready? DATA: lv_cnode TYPE REF TO cl_bsp_wd_context_node, lv_component TYPE string, lv_type TYPE i. TRY. TRY. lv_cnode ?= <cnode>-instance. DATA: lv_title TYPE string. lv_title = <cnode>-instance->if_bsp_model_binding~get_attribute( attribute_path = 'STRUCT.TITLE_ACA1' ). CATCH cx_sy_move_cast_error. * unexpected context node type -> consider field to be input ready! ENDTRY. READ TABLE rt_result INTO DATA(ls_firstname_mandatory) WITH KEY table_line = lv_dynamic_mandatory_field. IF lv_title <> '0002' AND ls_firstname_mandatory IS NOT INITIAL. DELETE rt_result WHERE table_line = lv_dynamic_mandatory_field. ELSEIF lv_title = '0002' AND ls_firstname_mandatory IS INITIAL. TRY. lv_cnode->if_bsp_model_util~disassemble_path( EXPORTING path = lv_attr_path IMPORTING name = lv_attr_name component = lv_component type = lv_type ). DATA: lv_disabled TYPE string. CASE lv_type. WHEN if_bsp_model_binding=>co_type_struct. IF lv_attr_name = 'EXT'. lv_disabled = lv_cnode->get_i_s_ext( component = lv_component ). ELSE. lv_disabled = lv_cnode->get_i_s_struct( component = lv_component ). ENDIF. WHEN if_bsp_model_binding=>co_type_simple. lv_disabled = lv_cnode->get_i_s_struct( component = lv_attr_name ). ENDCASE. CHECK lv_disabled IS INITIAL OR lv_disabled(1) CS 'F'. "means 'f', 'F', 'false' or 'FALSE' CATCH cx_sy_move_cast_error. * unexpected context node type -> consider field to be input ready! ENDTRY. lv_attr_value = <cnode>-instance->if_bsp_model_binding~get_attribute( attribute_path = lv_attr_path ). IF lv_attr_value IS INITIAL. * mandatory field is still initial APPEND lv_dynamic_mandatory_field TO rt_result. ELSE. * string is not initial -> field might still be initial depending on type lv_attr_meta = <cnode>-instance->if_bsp_model_binding~get_attribute_metadata( attribute_path = lv_attr_path ). IF lv_attr_meta IS BOUND AND lv_attr_meta->get_abap_type( ) CA 'DTIPFNbs'. * field type is Date, Time, Integer, Packed, or Float -> number passed IF lv_attr_value NA '123456789'. * mandatory field is still initial APPEND lv_dynamic_mandatory_field TO rt_result. ENDIF. ENDIF. ENDIF. ENDIF. CATCH cx_bol_exception INTO lv_exception. IF lv_exception->textid = cx_bol_exception=>entity_already_freed. DATA: lv_pattern TYPE string. CONCATENATE '//' lv_cnode_name '/*' INTO lv_pattern. * delete LT_MANDATORY_FIELDS where TABLE_LINE cp LV_PATTERN. ELSE. RAISE EXCEPTION lv_exception. ENDIF. ENDTRY. ENDIF. lv_last_cnode_name = lv_cnode_name. ENDMETHOD.
I’d like to mention that, this issue is already in our developer’s to do list. We should expect a standard solution to this issue in future. Just before the solution is released, you may try the above solution. And I didn’t test in more bsp components. Thus if there is anything I missed, welcome to add to this blog. Thank you!
Update on 2018 Jun 22nd
After more discussion at development team, if you like this function and need it in our standard, please raise suggestion in the upcoming Customer Connection project https://influence.sap.com/CRM2019 (from Oct 23 till Dec 3, 2018 ) and post an improvement suggestion there. Depending on the votes from other customers, development department will review the requests and decide if it can be implemented. Let’s vote it and make it true.