cancel
Showing results for 
Search instead for 
Did you mean: 

Ability to extend the templating process in Fiori Elements for oData v4?

carlonnheim
Participant
0 Kudos

Hi,

Is there a way to hook into the templating process for Fiori Elements for oData v4? We use FE extensively in our projects and it is serving us really well! The adaptation facilities provided in the "Flexible Programming Model" allows to model more or less any kind of application needed, but in some scenarios it results in duplication of code which is hard to maintain.

As a sample scenario, say we have a data point of particular interest in the solution domain, an "ArtefactID". Whenever these "Artefacts" appear across our application landscape, I want to render them as a Link field with a tailored popup (instead of the standard quick views) and some additional UI items around them. I also want to give them a dedicated filter control whenever they appear in a filter bar.

This scenario is easily solvable using the Flexible Programming Model - but it means I need to maintain a custom fragment for the field and similar for the filter control and inject the appropriate settings in the manifest of each app. If I want to compose these things further, such as showing them in a object page facet or in a combined column on the list report, I end up with further fragments and settings.

I am looking for a way to express something like this

entity SomeThing : cuid {
    //...
    @My.Vocabulary.Artefact: { something: 'interesting'}
    ArtefactID    : String;
}

And then declare an extension (e.g. in the manifest) which get called during templating to produce the control tree in the same way as the standard templating does.

I assume the standard templating process uses some form of plugin/factory technique to drive the rendering of the standard vocabulary items, but I cannot see a way to hook into it, is there any advice on this?

Thanks in advance!

//Carl

View Entire Topic
nicolas_lunet
Employee
Employee
0 Kudos

Hello Carl,

First of all, let me state that I like that question, it's a good one and I would like to have a good answer for it.

Unfortunately for you, today this is not something that we support, hooking into the template processing would be just as or even worse than in the manifest for you 🙂

That beings said, let me try to explain a bit more

Usually the way we use to determine that a property needs special handling is rarely due to it's property definition but rather because of the wrapper around it (DataField / DataFieldForAction ...), but sometimes we do rely on the specific property annotation, but that comes after the initial determination.

We could technically at that point allow for further custom processing but we don't, and even if we were to do it we probably would still require some common functionality from the control you use not to break the whole templating.

The other problem comes from even deciding to look for the custom template, OData annotations are very structured for genericity so your use case kind of defeats their purpose here.

Furthermore as you are using it only as a flag of some sort to be combined with a potential declaration on the manifest would that really solve your duplication problem ? The manifest declaration should still be there and the backend part would also need to be in each application.

I would be happy to discuss about this one further

Nicolas

carlonnheim
Participant
0 Kudos

Hi nicolas.lunet,

Happy to hear it caught your interest. I understand these constraints, I assume this would also need to entail some form of extensibility of the standard vocabularies with custom ones (we can annotate like that all we want in e.g. CAP, but only the ones fitting the vocabularies will anyway show in the $metadata).

Regarding manifest entries, yes some would be required for sure. The scaling/maintenance difference would come if I can replace by-field configurations (e.g. maintaining custom fragments) with something like a "sap.ui5"."extends"."sap.ui.templatingExtensions" in the manifest file (comparable to the controller extensions for the pages). If I have N fields in need of such custom treatment and they occur in M apps I typically end up maintaining N*M manifest configurations and often a fair number of fragments as well (since the fields often appear in different locations, bound through associations etc.). A well chosen extension API for these would bring the implementation concern to N or in fact often even lower (the number of distinct patterns or types among the fields, not the fields themselves), the manifests should only declare the use of the extension. The backend parts can also move quite far back in generalization through the use of for example CAP "aspects", type references, managed associations and so on.

Taking the vision one step further, it should be possible to package such implementation as well, so it can be reused through npm. E.g. include into a CAP project to bring vocabularies (if that is the mechanism, maybe there is a better way) and get a "com.acme.goodstuff" library/namespace into the UI side. Such separation of concern into the capability domain would be great for open source sharing of reusable FE extensions as well as customer-specific use cases.

Maybe we can connect to discuss further? I would be happy to contribute to realizing such a thing as well, to the extent we can from our side.

Thanks!

//Carl

nicolas_lunet
Employee
Employee
0 Kudos

Those are interesting options and I think we would need to go further in details to see what can be done to solve them, we should definitely connect, you can follow me back here and supposedly we can connect 🙂

Marian_Zeis
Active Contributor
0 Kudos

That is really a good idea Carl for sharing those custom annotations via npm.

But I understand that would be hard to implement.

Everything started with a vision so you can still hope and dream.

Totally support this idea!

carlonnheim
Participant
0 Kudos

Hi nicolas.lunet and 20eed143c19f4b82bc4cf049916102cb , sounds good to connect and discuss further. I tried sending you a PM with my contact details Nicolas, but not sure it arrived?

Meanwhile, I tried a very basic experiment by adding the following to DisplayStyle-dbg.js.

/**
* Entry point for further templating processings.
*
* @param internalField Reference to the current internal field instance
* @returns An XML-based string with the definition of the field control
*/
getTemplate: internalField => {
  // START
  if (internalField?.property?.annotations?.Common?.SemanticObject) {
    /**
     * Design a way to declare through annotations which control should be tried,
     * could for example be a manifest-entry indicating the namespace of control extensions
     * and a convention to use that namespace + the semantic object
     */
    const sControl = `sap/fe/featureShowcase/mainApp/control/${internalField.property.annotations.Common.SemanticObject}`;
    const oClass = sap.ui.require(sControl);
    /**
     * Design an interface the controls should comply with, could be a getTemplate method accepting the xml templater and internalField
     * If it does, delegate templating to the control
     */
    if (oClass?.prototype?.getTemplate) {
      return oClass.prototype.getTemplate(xml, internalField);
    }
  }
// END

and then implementing a sample control like so

sap.ui.define([
    'sap/m/Link',
    'sap/fe/macros/field/FieldTemplating',
    'sap/m/MessageBox',
], function (
    Link,
    FieldTemplating,
    MessageBox,
) {
    'use strict';

    return Link.extend("sap.fe.featureShowcase.mainApp.control.FeatureShowcaseChildEntity2", {
        metadata: {
            manifest: "json"
        },
        renderer: {},

        // Implement the interface dictated by the templating process
        getTemplate: function (xml, internalField) {
            const text = FieldTemplating.getTextBinding(internalField.dataModelPath, internalField.formatOptions, true);

            return xml`<myApp:FeatureShowcaseChildEntity2
                xmlns="sap.m"
                xmlns:myApp="sap.fe.featureShowcase.mainApp.control"
                xmlns:core="sap.ui.core"
                id="${internalField.noWrapperId}"
                text="${text}"
                visible="${internalField.displayVisible}"
                wrapping="${internalField.wrap === undefined ? true : internalField.wrap}"
                ariaLabelledBy="${internalField.ariaLabelledBy}"
                emptyIndicatorMode="${internalField.emptyIndicatorMode}"
                customData:loadValue="${internalField.valueAsStringBindingExpression}"
            >
            </myApp:FeatureShowcaseChildEntity2>`;
        },

        // Simple example of a custom behavior -> show a MessageBox on click
        init: function () {
            this.attachPress(oEvent => {
                MessageBox.show('I am a custom control, injected in a FE template');
            });
        },
    });
});

This makes the field annotated with that Semantic Object always template as the implemented custom control. For example in a table

or on an object page

I did not try other locations (e.g. combined fields etc.). There will be more to this I am sure, but hopefully feasible?

The sample is on top of the Feature Showcase if that helps. Note that you need to run with debug sources for the overlaid DisplayStyle-dbg.js to be found - i.e. http://localhost:4004/?sap-ui-debug=sap/fe/macros#FeatureShowcase-manage

Regards

//Carl

nicolas_lunet
Employee
Employee
0 Kudos

Hi Carl,

I got your message just didn't have the time to answer yet 🙂

This approach works of course but it's maybe a bit too complex and specific to get where you want.

A few questions meanwhile here
- Is it only for display mode or do you also want to extend the edit style template ?

- Is it limited to semantic object or is that an arbitrary choice here ?

Regards

Nicolas

carlonnheim
Participant
0 Kudos

Hi Nicolas,

Answers to your questions:

- Is it only for display mode or do you also want to extend the edit style template ?

I think display mode is the main use case. Editing values which are keys to something else will typically come down to a Value Help of some sort and the Common.ValueList annotation supports that quite well. There are probably scenarios where a customized value help is useful too, but I would think much less frequently than the display case.

- Is it limited to semantic object or is that an arbitrary choice here ?

This is probably the most important design aspect of this. As you noted in your first comment, oData annotations are generic and even though I can annotate my CAP model freely as in the original post it will not pass anything outside of the official vocabulary through to the $metadata xml. So, to make the above example, I looked for something suitable in the UI Vocabulary. Using the SemanticObject like that would work in many scenarios, but I believe using a dedicated annotation is probably better if that can be done.

Two use-case examples:

Example 1: I am working with a one-off solution in a Professional Services company, I might have a "Project" entity as a central business object. If I can make anything annotated as a Project SemanticObject pick up a particular control, I can realize for example a more elaborate popup than the regular QuickView wherever the key to a "Project" appears in my apps.

Example 2: I am providing a third party service with location-tracking IoT devices, I want to provide an opaque "my.company.GeoTag" reuse control for the consumers of my service to use in their apps, I do not want to interfere with the rest of the consumers app structures.

So, if extending the Vocabulary with new terms is feasible, I would think that is the better option (supports both examples). The instruction to my consumers, if I am again the third-party provide in example 2, would be to make an entity definition like so (assuming a UI.Representation tag is added to the Vocabulary)

entity Parcel {
  key ParcelID : String;

  @UI.Representation: {
    Control: 'my.company.GeoTag', // Use the third-party reuse control
    Properties: [
      // Map local properties into the third party control
      { LocalProperty: ParcelID, RepresentationProperty: 'Title' },
    ]
  }
  thirdPartyTagUUID : UUID; // An opaque ID referring to the third-party service
}

and that should give them the UI representation I have implemented. Using the CAP packaging features further, it could probably even boil down to doing "npm i @mycompany/LocatorService" and then something like this (encapsulating everything but the property mapping into the imported package)

using locator from '@mycompany/LocatorService';

entity Parcel {
  key ParcelID : String;

  @UI.Representation.Properties: [
    { LocalProperty: ParcelID, RepresentationProperty: 'Title' },
  ]
  thirdPartyTag : Association to locator.GeoTag;
}

Hope this makes sense. Thanks!

//Carl