Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member
0 Kudos

What's wrong with OVS?

Besides all the excellences, built-in WebDynpro OVS component has several drawbacks:

  • It is always shows _all_ attributes both for filter and for output table. To overcome this limitation you have to develop specific context nodes that contain only necessary attributes (rather then probably existing one). There is no workaround if you bind OVS directly to ICMIQuery (actually, ESF query, see below).
  • It is impossible to define order of filter UI controls and/or table rows.
  • OVS has bug/feature that requires you to have at least one field in filter, otherwise it doesn't work.
  • OVS and Adaptive RFC integrates badly, you have to use context nodes in between.

I guess first three statements are self-explanatory. Here are some comments to the latest statement.

Imperfect Adaptive RFC

You have probably noticed, there are two special classes per every RFC in Adaptive RFC model: one holds all input parameters (primitives, structures) plus table parameters; it has name RFC_NAME_Input. Second holds all output parameters (primitives, structures) plus tables; it is RFC_NAME_Output. Btw, input table parameter in RFC may activate bug in Adaptive RFC - same tables are repeated for output as well though they should not be there. The only "special" about these classes is that they have no corresponded STRUCTURE in ABAP. They are just logical "request" and "response".

Ok, but query in Adaptive RFC is ICMIQuery, and every ICMIQuery has "Result" property of type java.util.Collection. In ESF model this property is actually a collection (IAspect) that holds all result rows. So you can easily bind OVS over ESF query and... you are done!

However, in Adaptive RFC this collection contains only single element - the response. And typically you need to show some table from response. OVS + ICMIQuery does not help here. You have to use a pair of context nodes, bind input to one node, output to second. Binding output assumes that you track onQuery notification of IWDNotificationListener. If your input has complex structure (some input parameters are STRUCTURE in ABAP), then you have to collect all necessary attributes to one node, then copy back and force between node element and model object. Same is true for output. Btw, WDCopyService is of no help here.

Not invented here...

Having done OVS+RFC value helps too many times, I finally lost my patience. There are should be a generic way! If it does not exist, then it should be developed!

You may find download of complete library here, below is just usage samples and snippets that outlines features.

So the main idea is to create wrapper that implements ICMQuery in quite specific way. To make description concrete, let us consider BAPI_FLIGHT_GETLIST as an example.

  1. All input parameters of query are proxing original attributes in *_Input class or any nested class reachable by 0..1 / 1..1 relation. Code has also experimental support for 0..n / 1..n relations, in this case only first "row" is used. So for BAPI_FLIGHT_GETLIST you may define property "Airline", "Max_Rows", "Departure_From.City", "Departure_To.Countr" etc. Notice that attributes from related objects are prefixed by target role name. You can see this name in NW IDE when either mapping model objects, or viewing them in model class viewer.
  2. The way properties are defined in this wrapper allows developer to define order of fields, as well as customize certain UI options (labels, tooltips, value help). Worth to mention that only attributes you explicitly enumerated get into wrapper. If you supply "*" as attribute name then you collect attributes from all nesting levels of input into wrapper.
  3. The result of query is concrete table parameter, so ICMIQuery.getResult() returns true collection, rather then "response" singleton. So for our example we specify "Flight_List". You can define what fields you'd like to show in this table. Exactly same rules applies here, the only point that deserves attention is that paths are related to table entry class (Bapisfldat in this example).
  4. Again, input objects tree and output rows are lightweight, i.e. they are just stateless wrappers over real object(s). As a side effect, any changes in wrapper are immediately available in original objects; any changes in original objects are visible if you are using wrappers.

Now take a look at the code that adds OVS help to content attribute Input.Airline, showing departure/arrival city/country and airline/number of rows as filter and outputting flights list (airline, price, date/time) as result:

RFCValueHelp.bind( wdContext.getNodeInfo().getChild("Input").getAttribute("Airline"), RFCQueryClass.wrapQuery( new Bapi_Flight_Getlist_Input(), new Object[]{ RFCQuery.p("Destination_From.City").label("From city").info("Departure city"), RFCQuery.p("Destination_From.Countr").values(svsCountries), "Airline", // -------------------- RFCQuery.p("Destination_To.City").label("To city").info("Arrival city"), RFCQuery.p("Destination_To.Countr").values(svsCountries), "Max_Rows" }, "Flight_List", new String[]{"Airline", "Price", "Flightdate", "Deptime"} ), new WDOVSNotificationAdapter() { public void applyResult(final IWDNodeElement el, final Object row) { final Bapisfldat entry = (Bapisfldat)row; el.setAttributeValue("Airline", entry.getAirlineid() ); } } );

 

Actually, what you see is just sentence above code written in Java (rather then in bad English 😉

Pay attention, that you can either define field by it's path (String), or wrap it into ICCMIPropertyDef implementation with further customization.

image
 
Image 1. Airline value help with Flight_List RFC

If you plan to use number of separate queries of the same Java class, you may use more verbose form:

// Create wrapper "class" final RFCQueryClass rfcQueryClass = RFCQueryClass.compile ( Bapi_Flight_Getlist_Input.class, new Object[]{ RFCQuery.p("Destination_From.City").label("From city").info("Departure city"), RFCQuery.p("Destination_From.Countr").values(svsCountries), "Airline", // -------------------- RFCQuery.p("Destination_To.City").label("To city").info("Arrival city"), RFCQuery.p("Destination_To.Countr").values(svsCountries), "Max_Rows" }, "Flight_List", new String[]{"Airline", "Price", "Flightdate", "Deptime"} ); ... RFCValueHelp.bind( wdContext.getNodeInfo().getChild("Input").getAttribute("Airline"), rfcQueryClass.wrapQuery(new Bapi_Flight_Getlist_Input()), new WDOVSNotificationAdapter() { public void applyResult(final IWDNodeElement el, final Object row) { final Bapisfldat entry = (Bapisfldat)row; el.setAttributeValue("Airline", entry.getAirlineid() ); } } );

 

Use this only when you are creating several wrappers by separate queries, while this way you save processor time/memory on metadata construction. Actually, I rare see the case for this functionality: it is completely save to use same Adaptive RFC (and hence my wrapper) between several OVS extensions while OVS window is modal. Just do not forget to reset input structure in applyInput hook of IWDNotificationListener!

Having RFCQuery variable may give you additional benefits:

final RFCQueryClass rfcQueryClass = RFCQueryClass.compile(...) final RFCQuery rfcQuery = rfcQueryClass.wrapQuery( new Bapi_Flight_Getlist_Input() ); rfcQuery.setAttributeValueByPath( "Destination_From.Countr", "DE" ); rfcQuery.setAttributeValueByPath( "Destination_To.Countr", "US" ); rfcQuery.setAttributeValueByPath( "Max_Rows", new Integer(20) );

 

So you can attributes by path, rather then via traversing relations. If you still wondering whether this is useful, consider the fact that RFCQuery will automatically create related objects on first use; and path can be of any depth. Though, you can only refer paths that you use during class construction.

If you want to try Flight_List example, here is code that returns map of countries (quite naive, I know):

private Map countries() { final Locale appLocale = WDResourceHandler.getCurrentSessionLocale(); wdComponentAPI.getMessageManager().reportSuccess( "Current locale: " + appLocale.getDisplayName(appLocale) ); final Collator collator = Collator.getInstance(appLocale); final TreeMap countries = new TreeMap( new Comparator() { public int compare(final Object k1, final Object k2) { return collator.compare(k1, k2); } } ); final Locale[] locs = Locale.getAvailableLocales(); for (int i = 0; i < locs.length; i++) countries.put( countryName(locs[i], appLocale), locs[i].getCountry() ); final LinkedHashMap result = new LinkedHashMap(); for (final Iterator i = countries.entrySet().iterator(); i.hasNext(); ) { final Map.Entry e = (Map.Entry)i.next(); result.put( e.getValue(), e.getKey() ); } return result; } private String countryName(final Locale locale, final Locale targetLocale) { final String name = locale.getDisplayCountry(targetLocale); if (null == name || name.length() == 0) return locale.getDisplayCountry(Locale.ENGLISH); else return name; }

 

Current issues and limitations

  1. As noted above, it is impossible to use paths not mentioned during class construction. Need a workaround for this.
  2. It is bit unclear how to traverse multiple relations. Currently only first row is created/used. Btw, you can try to add the following when creating wrapper:
    RFCQuery.p("Date_Range.Low").label("Date from") RFCQuery.p("Date_Range.High").label("Date from") ... new WDOVSNotificationAdapter() { public void applyInputValues(IWDNodeElement el, Object query) { final Bapi_Flight_Getlist_Input q = (Bapi_Flight_Getlist_Input)query; // THIS CALL CREATE ALL RELATED OBJECTS ON PATH rfcQuery.setAttributeByPath("Date_Range.Low", null); Bapisfldra range = (Bapisfldra)q.getDate_Range().get(0); range.setOption("BT"); range.setSign("I"); } public void applyResult(final IWDNodeElement el, final Object row) { /* Same as in example* / } }
     
  3. You can trap some tricky bug, if you wrapping some existing attribute from nested relation, but tries to access non-wrapped attribute from same relation. Depending on call sequence corresponding related object may or may not be created (see workaround above). Be aware of this!
  4. There is an option that exploits OVS bug: if you pass null as path during wrapper construction (input only) OVS will sometimes set placeholder on form. Currently OVS uses 3 columns for fields. But if it finds attribute with non-simple type, it increments columns number without creating control. So you may get "carriage return" effect. Btw, if your input has no attributes at all, set single null as attribute to avoid OVS bug mentioned at the beginning of article.

Download current source code here, rename to *.zip after downloading.

2 Comments