Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
rdiger_plantiko2
Active Contributor

In our days, input fields supported by autocompletion are omnipresent in the web. In this blog, I will show how to implement an input which gets autocompleted with proposals from an SAP system.

The basic idea of an autocompleter is to provide the input field automatically with special key handlers, triggering Ajax requests. The requests return with input proposals that are presented as a list and can be selected with a mouse click (or with keys like "enter" or "tab").

Most JavaScript frameworks provide some tools for autocompletion. jQuery, one of the most comon JavaScript frameworks, supports autocompletion in the form of a widget of the jQuery UI layer. My example uses this widget. The code is loaded into the browser from Google's CDN for jQuery:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.js"></script>

Let me start with the live example, implemented as a Business Server Page:

http://bsp.mits.ch/buch/zdemo_autocomp/main.do

Here is how it looks in action. I just have typed the letter "z", and the SAP system proposes all sales agents whose name contains a "z":

jQuery's way to activate a widget - or any plugin function - is to select elements with a selector, and then apply the plugin function to it. The function usually accepts an hash as argument, containing the widget options.

It's free to us which selector we would like to use. A selector could enumerate all the input fields that should be attached to autocompletion as a comma-separated list. But it is much more elegant to use a special CSS class like "autocomplete". The autocompletion code then writes - in its simplest form:

$(function() {
     $(".autocomplete").autocomplete({
          source:"autocomplete.do"
        });
  });

The envelopping $( function() {  ... }) is the signal for jQuery to register this code for the special event "DOM loaded": The earliest point in time during page loading when the complete HTML DOM is parsed and therefore accessible to JavaScript. The function $(".autocomplete") selects all elements in the current page having CSS class "autocomplete", this array being wrapped in a jQuery object. Applying the autocomplete() function to this object will register keyup and click events of the corresponding fields. The "source" option specified in the option argument of the function call may specify an URL (if it is of type string, it will be considered as an URL). You may also specify a self-defined JavaScript function f(request,response) instead, or even an array with the data, if you already have them on the client.

In our case, we address a special controller "autocomplete.do", which for simplicity is located in the same BSP application as the example controller. In a more real-life example, the autocomplete service would be attached as a request handler to a special SICF node: Since the BSP framework is of no use for its functions, a simple request handling class is completely sufficient.

When the user types some characters, Ajax request to "autocomplete.do" will be performed, with a Get parameter "term" containing his actual input (by the way, you can specify with the minLength parameter that the requests should not start before the user has typed a certain amount of characters in the input field).

The response should be sent as a JSON string, in the form of an array of objects, each object containing a "label" element with a description, and a "value" element to be taken over into the input field. Here is an example of the expected format as is required by jQuery's autocomplete function:

[
  {
    "label": "Luigi Hunziker",
    "value": "5"
  },
  {
    "label": "Ernst Ziegler",
    "value": "6"
  },
  {
    "label": "Jakob Kurz",
    "value": "28"
  },
  {
    "label": "Philippe Zobler",
    "value": "36"
  }
]

On the ABAP site, we can collect our results into an internal table of "name-value" pairs. Although they have slightly different semantics, the table type TIHTTPNVP together with its corresponding line structure IHTTPNVP fit good for our purpose: IHTTPNVP simply consists of two components of type string - NAME and VALUE.

When using the new built-in transformation from ABAP to JSON, the code for transforming arbitrary ABAP data into an UTF-8 encoded string (which must be of type XSTRING, since the ABAP codepage of a unicode system is not UTF-8 but UTF-16LE), always looks like this:

method nvp_to_json.

   data: lo_json type ref to cl_sxml_string_writer.

   lo_json = cl_sxml_string_writer=>create( if_sxml=>co_xt_json ).
   call transformation znvp2autocomp
     source data = it_nvp
     result xml lo_json.
   ev_json = lo_json->get_output( ).

endmethod.

The only part of the code which is special to our situation is the XSLT transformation znvp2autocomp:

<xsl:transform version="1.0"
   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:strip-space elements="*"/>

<xsl:template match="DATA">
   <array>
     <xsl:apply-templates/>
   </array>
</xsl:template>

<xsl:template match="IHTTPNVP">
   <object>
     <str name="label"><xsl:value-of select="NAME"/></str>
     <str name="value"><xsl:value-of select="VALUE"/></str>
   </object>
</xsl:template>

</xsl:transform>

With this transformation, we map the internal representation of ABAP data of type TIHTTPNVP into the internal XML representation a JSON object: An <array>, containing inner <object>'s, each with two elements, "label" and "value".

The request handling of the controller autocomp.do consists of three actions:

  • Perform the search based on the user input (form field "term"), populating an internal table of name/value pairs
  • Transform the result into JSON in the format that is required by jQuery
  • Put the result into the response data, and set the appropriate response content type.

Here is the code for this.

method DO_REQUEST.

   data: lt_results type tihttpnvp,
         lv_data type xstring,
         lv_term type string.

   lv_term = request->get_form_field( 'term' ).

   do_search( exporting iv_term    = lv_term
              importing et_results = lt_results ).

   nvp_to_json( exporting it_nvp   = lt_results
                importing ev_json  = lv_data ).

   response->set_data( lv_data ).
   response->set_content_type( 'application/json; charset=UTF-8' ).

endmethod.

The do_search() method has no surprises: It refines some example data, based on the string iv_term:

method do_search.

   data: lt_sales_persons type zverknr_tab_demo,
         ls_result type ihttpnvp.

   field-symbols: <ls_sales_person> type zverknr_struc_demo.

   perform get_data in program saplzf4_exit_demo
           changing lt_sales_persons.

   loop at lt_sales_persons assigning <ls_sales_person>
        where verknr cs iv_term
           or name cs iv_term.
     ls_result-name  = <ls_sales_person>-name.
     ls_result-value = <ls_sales_person>-verknr.
     append ls_result to et_results.
   endloop.

endmethod.

That's all which is to be said about this implementation on ABAP site.

Actually, when using autocompletion systematically, do_search( ) could be a method of an interface, with that same signature - an input iv_term and a result et_result of type tihttpnvp - and there may be different implementations for different search types (or data types). A general autocompletion handler gets the search id as a further parameter and provides the corresponding search instance, using an object factory. The rest of the code looks as above. Actually, this is the way we have implemented autocompletion in a recent project.

Some remarks on my usage of jQuery's autocompletion widget on the client side are in order:

For accessibility reasons, there is a special so-called liveRegion which will be automatically created by jQuery's autocompletion widget. It might be interesting to know that this region, once created, can be reused for displaying other data. In my example, I want to put the value into the input field, and the label for this value should be displayed to the right, just in the liveRegion. For this, I access the widget in the "select" event which is called when the user selects one item from the menu by clicking:

$(".autocomplete").autocomplete({
  ...
  select:function(event,ui) {
              $(this).data("ui-autocomplete")                .liveRegion.text( ui.item.label );              }
  });

A second, lesser known point: It is possible to redefine the function which renders the menu items. In my example, I changed this function in such a way that it displays not only the label but the combination of value and label, separated by a hyphen:

$(".autocomplete").autocomplete({ ... })
  .data( "ui-autocomplete" )._renderItem = function( ul, item ) {
        return $( "<li></li>" )
            .data( "item.autocomplete", item )            .append( "<a>" + item.value + " - " + item.label +"</a>" )
            .appendTo( ul );
         };

But these are peculiarities. The straightforward implementation is simple - on the ABAP side as well as on the HTML/JavaScript side, as I hopefully showed with this blog.

13 Comments
Labels in this area