Skip to Content

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”:

/wp-content/uploads/2013/04/autocomp_203102.png

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.

To report this post you need to login first.

13 Comments

You must be Logged on to comment or reply to a post.

    1. Rüdiger Plantiko Post author

      Thank you, Umit!

      Yes – with the advent of the built-in JSON transformations, XSLT is the right tool for transforming into JSON datatypes that are required by other parts of the software. Here, jQuery requires an “array of hashes” (AoH) format with given names “label” and “value” (in lower case, unfortunately – were they upper case, the identity transform and a DDIC structure with components LABEL and VALUE would already do the job).

      If you want to see more XSLT applications of this kind, you may find my blog on REST APIs interesting.

      Regards,

      Rüdiger

      (0) 
  1. Steve Oldner

    Great Blog post.  I stumbled across this looking for something else and am happily surprised!

    I have just getting started using JQuery and BSPs so this will be a definite plus!

    Thanks Rudiger!

    (0) 
    1. Rüdiger Plantiko Post author

      Hi Steve,

      I am glad that the post could be useful. My company is currently in a mega project with a client based on stateless BSP with jQuery. If I find some more interesting aspects in the implementation phase, I will keep blogging them!

      Regards,

      Rüdiger

      (0) 
      1. Steve Oldner

        Danke!

        We are currently writing a mobile web portal for selected HR stuff using BSPs with HTML5 and JQuerymobile.  I compliment you on your English.  My German is very rusty.

        Looking forward to more posts! 

        (0) 
  2. Andrew Barnard

    Hi Rüdiger,

    I recently tried re-implementing this within a BSP using HTMLB extensions.

    As you mention in your blog, your example code uses a special CSS class autocomplete to enumerate the input fields that should be attached to autocompletion. I found it practical in the BSP with HTMLB extension context to address the field ID directly e.g. “#userName” to attach to the field userName rather than the “.autocomplete”.

    I also found the rendering of the autocompletion list was pretty ordinary until I included the query-ui.css – in my case taken from http://code.jquery.com/ui/1.9.0/themes/smoothness/jquery-ui.css. I notice your example BSP at your website includes a custom CSS, which I presume is a cut-down version of this.

    Thanks for an excellent post.

    (0) 
    1. Rüdiger Plantiko Post author

      Hi Andrew,

      thanks for your reply.

      Of course, you may replace the selector $(“.autocomplete”) by any other selector whatever.

      Like it or not – I have made the CSS of the autocompletion list compatible with the rest of the layout of the web site http://bsp.mits.ch – I know that, the good web developer you are, you can make the look and feel much, much cooler.

      I am very happy to see, however, that the example successfully exposed the implementation idea (since you got it, apparently).

      In our current web project, we have a <z:input> BSP element with an attribute “autocomplete”. The value of that attribute, if given, contains the key of a factory entry for a class which implements the autocompletion. For example,

        <z:input binding=”//offer/gv_sample_article” autocomplete=”matnr”>

      will render the <input> element with CSS class “autocomplete” and with another element attribute specifying the autocomplete method. The request handler gets this additional attribute and serves the autocompletion request with an instance of the class which corresponds to that key.

      So for me it was convenient to have the set of all input fields under autocompletion being available with the selector “input.autocomplete”.

      Regards,

      Rüdiger

      (0) 
  3. kuo wai soh

    Hi Rüdiger,

    Great blog, your blog helped a lot. But I do have few query that come into my mind, would appreciate if I could ask you in private?

    Thanks

    Regards

    Soh

    (0) 
    1. Rüdiger Plantiko Post author

      Hi Soh,

      in the spirit of the ABAP community, I would prefer that you ask your question publicly. This gives others the chance to contribute and/or to learn, too.

      With kind regards,

      Rüdiger

      (0) 
      1. kuo wai soh

        Hi Rüdiger,

        Sorry about that. What I need to ask is regarding the BSP Extension  being use, are those extension self developed or we can find it else where?

        Regards

        Soh

        (0) 
        1. Rüdiger Plantiko Post author

          Hi Soh,

          yes, it’s a common practice of our team to use project-specific custom BSP extensions, mainly for rendering the document skeleton and the user controls (tables, input fields, select boxes and so on) in the specified fashion, so the ABAP developers can focus on implementing the business logic and passing the data to the custom tags.

          In the current project, we have a <zpmr:input> element responsible for rendering the HTML input fields. If we want to attach an autocompletion, we have to pass the attribute “autocompMethod”, like so:

                 <zpmr:input id           = “offer_fastsearch”

                             label         = “<%=otr(ZPMR_CAMPAIGN/FAST_SEARCH)%>”

                             binding      = “gv_offerid”

                             autocompMethod = “offer”/>

          The autocompMethod attribute specifies the desired autocompletion method. Basically, this results in an HTML rendering like this:

          <input type=”text” id=”offer_fastsearch” value=”(filled from binding)”  class=”pmrInput autocomp” data-autocomp-method=”offer” …>

          The rest is performed on the client, similar to my description in the blog:

          • All “.autocomp” elements are registered for autocompletion.
          • The autocompletion method is passed as additional parameter with the Ajax request,
          • and a general autocompletion request handler in ABAP uses this parameter as entry for an object factory,
          • delegating the query to the corresponding object created by the factory.

          Unfortunately, the code of our ZPMR project is not publicly available, but I hope that I could give you the idea.

          Regards,

          Rüdiger

          (0) 
  4. Srinivas Jayanna

    Hi Rüdiger,

    I have similar requirement in CRM Web UI. Where I have two views(Search and Result View  ). In search view i have html input field with auto complete option.

    I am able achieve auto complete with below code. But table view is not binding when i am using SRC file reference in Script tag(Which mandate for auto complete). I have pasted code below which i am using in .htm page which link to my CRM component.

    I have kicked of discussion in scn forum but not got any response yet. 

    CRM – Data is not binding to table view when script tag with auto-complete .js file reference added to .htm page.

    Please throw some suggestion on this.

    <link rel=”stylesheet” href=”//code.jquery.com/ui/1.11.0/themes/smoothness/jquery-ui.css”></link>

    <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> 

    </html>

    <style>

      .ui-autocomplete {

        max-height: 400px;

        overflow-y: auto;

        /* prevent horizontal scrollbar */

        overflow-x: hidden;

      }

      * html .ui-autocomplete {

        height: 500px;

      }

      </style>

      <script>

      $(function() { var availableTags = [

    <% LOOP AT ZL_ZGEN_PRO_SEARCHVIEW_IMPL=>gt_pdes INTO ls_pdes. %>

                          <% lv_t1 = ls_pdes-SHORT_TEXT.%>

                          <% lv_id = lv_id + 1. %>

                          <% IF lv_id LT ZL_ZGEN_PRO_SEARCHVIEW_IMPL=>gv_lines. %>

                           “<%= lv_t1 %>”,

                          <% ELSE. %>

                            “<%= lv_t1 %>”

                          <% ENDIF. %>

                          <% ENDLOOP.%> ];

      function split( val ) {

          return val.split( /,\s*/ );

        }

        function extractLast( term ) {

          return split( term ).pop();

        }

        $( “#tags” )

          .bind( “keydown”, function( event ) {

            if ( event.keyCode === $.ui.keyCode.TAB &&

                $( this ).autocomplete( “instance” ).menu.active ) {

              event.preventDefault();

            }

          })

          .autocomplete({

            minLength: 0,

            source: function( request, response ) {

              response( $.ui.autocomplete.filter(

                availableTags, extractLast( request.term ) ) );

            },

            focus: function() {

              return false;

            },

            select: function( event, ui ) {

              var terms = split( this.value );

              terms.pop();

              terms.push( ui.item.value );

              terms.push( “” );

              this.value = terms.join( “, ” );

              return false;

            }

          });

      });

      </script>

    (0) 

Leave a Reply