Wednesday, May 20, 2015

How to create a Guided Activity in the CRM WebClient

One user interface element that the CRM 7.0 system offers and which is perhaps not so well known is the Guided Activity. It's also known as Task-Based User Interface or Road Map. In this blog I would like to show how to create and use such a Guided Activity so you might see the potential for your own environment.



The use case


Let's say we want to have an application in which a user can make changes to multiple orders. To guide the user we want to split up to process into the following tasks:
  • Search for orders
  • Modify the selected orders
  • After the system checks the changes evaluate the result
  • Save the changes

Setting up the application


Start the UI Component Workbench and create a new component with a main window. Assign the appropriate model using the Runtime Repository Editor and assign the main window to the ComponentInterface to make it available in the WebClient customizing.

To enable data binding within all views of the application it's good practice to create a Custom Controller for this purpose. Let's create one with the following context nodes:

A SEARCH-node with reference to the dynamic query object for orders
An ORDER-node for storing the collection of selected orders

Since we divided the process into four tasks we start by creating one view per task:

Search view
When you look at the possible options for creating a new view, one of them is to create a Search Page. Since I only want a search view (and this option creates a view set including a search and a result view) I choose to create a simple view with one context node called SEARCH which is based on a dynamic query object. The view type, for now, is an "Empty View". After the wizard finishes we can convert it to a standard search view. Don't forget to bind the SEARCH-node to the custom controller.

List view
This is a view with one context node ORDER (also bound to the custom controller) and has a view type "Table View". Make sure to mark the property Configurable to enable easy configuration of the view layout. Because this will be the view in which the user can make modifications I set the property editMode to ALL in the HTML-code.

Overview and Confirmation view
These two views are created similar to the List view but without adjustment of the editMode. The important settings are:


Now that we have the building blocks in place it's time to create the actual view for the Guided Activity:

Roadmap view
Create a new view of type "Guided Activity Page" (there is no wizard this time). Create context nodes SEARCH and ORDER and bind these to the custom controller. Now include this view in the main window and assign the above created views to the Roadmap view. The Runtime Repository Editor should look like this:



Configuring the application


Before being able to test the application we need to configure each view. We start with the Roadmap view. Select all the views and provide a label for each step:


Perform the necessary configuration for the other views as well. Now after all this hard work it's possible to test the application!


As you can see all the steps we've defined are available and you can already navigate back and forth between the steps. However, no data is selected and no checks are made. This part is described below.

Implementing the business logic


But what is a good place to store the business logic? Looking at "Navigation Between Guided Activity Steps" in the SAP help you'll see that the system provides two interfaces to influence the behavior: IF_BSP_DLC_PROCESS_CONTROL which the Roadmap view implements by default and interface IF_BSP_DLC_PROCESS_STEP which the individual steps might implement. So you have a choice here but I strongly recommend to use only the provided interface in the Roadmap view and in doing so keep the logic in one place at much as possible.

Method IS_POSSIBLE_TO_LEAVE_CURR_STEP is crucial because the system will check at each navigation attempt, by calling this method, whether the requested navigation is allowed. Within this method the system provides (through attribute ROADMAP_CONTROL information on the current and target step and provides methods to enable and disable steps.

To initialize the Guided Activity properly I want to disable the last two steps by default so they can be activated later if relevant. The following code in method DO_PREPARE_OUTPUT takes care of this:

  method do_prepare_output.

    super->do_prepare_output( iv_first_time ).

    "When starting the roadmap deactivate direct navigation to the last two steps
    if me->roadmap_control->get_active_item_number( ) = 1.
      me->roadmap_control->set_item_disabled( 3 ).
      me->roadmap_control->set_item_disabled( 4 ).
    endif.

  endmethod.


Now it's time to implement the logic for each transition. I prefer to create a method per transition to get a nice and clear overview of the navigation possibilities. The code then looks something like:

  method if_bsp_dlc_process_control~is_possible_to_leave_curr_step.

    "Concatenate the current and target step in one variable
    case me->roadmap_control->get_active_item_number( ) && '-' && me->roadmap_control->get_target_item_number( ).

      when `1-2`"Search -> List
        "Navigation is only allowed if orders have been found
        if search_orders( ) = 0.
          rv_result = abap_true.
          "Activate the Overview step
          me->roadmap_control->set_item_disabled( iv_item_number = 3 iv_disabled = abap_false ).
        endif.

      when `2-3`"List -> Overview
        "This navigation is always allowed
        rv_result = abap_true.
        if check_orders( ) = 0.
          "Activate the Confirmation step if all changes can be processed
          me->roadmap_control->set_item_disabled( iv_item_number = 4 iv_disabled = abap_false ).
        endif.

      when `3-4`"Overview -> Confirmation
        "This navigation is allowed since all checks have already been done in the previous step
        rv_result = abap_true.
        "Save the changes
        if save_orders( ) = 0.
          "Ready, no way back
          me->roadmap_control->set_item_disabled( 1 ).
          me->roadmap_control->set_item_disabled( 2 ).
          me->roadmap_control->set_item_disabled( 3 ).
        endif.

      when `4-1` or `4-2` or `4-3`
        or `3-1` or `3-2`
        or `2-1`.
        "Backwards navigation is always allowed
        rv_result = abap_true.
        "Deactivate the next step
        me->roadmap_control->set_item_disabled( me->roadmap_control->get_active_item_number( ) + 1 ).

    endcase.

  endmethod.

The result


With all the navigation logic in place the happy flow of the Guided Activity is presented like this:





Notice how steps 3 and 4 become available when applicable and that the Finish button is automatically activated at the end. Pressing it will restart the Guided Activity.

How to create a search view


As a bonus I want to show you how to convert an empty view into a search view in a few steps:
  • Start by changing the inheritance of the view controller class. Check where the class inherits from CL_BSP_WD_VIEW_CONTROLLER and change it to CL_BSP_WD_ADVSEARCH_CONTROLLER
  • Similarly change the inheritance from the SEARCH-node from CL_BSP_WD_CONTEXT_NODE to CL_BSP_WD_CONTEXT_NODE_ASP.
  • Lastly, provide the Search view with the following HTML-code:

    <thtmlb:searchArea>
      <thtmlb:advancedSearch id            "search"
                             fieldMetadata = "<%= controller->get_dquery_definitions( ) %>"
                             header        = "<%= SEARCH->get_param_struct_name( ) %>"
                             fieldNames    = "<%= controller->get_possible_fields( ) %>"
                             values        = "//SEARCH/parameters"
                             showMaxHits   = "false" />
    </thtmlb:searchArea>

Et voilĂ :

The coding in the SEARCH method is then as simple as:


  method search_orders.

    data:
      lr_query  type ref to cl_crm_bol_dquery_service,
      lr_result type ref to if_bol_entity_col.

    "Execute the query and store the result
    lr_query ?= me->typed_context->search->collection_wrapper->get_current( ).
    lr_result = lr_query->get_query_result( ).
    me->typed_context->order->set_collection( lr_result ).

  endmethod.

No comments:

Post a Comment