2

abap2UI5 – (5/7) Extensions with XML Views, HTML, JS & Custom Controls

 11 months ago
source link: https://blogs.sap.com/2023/04/12/abap2ui5-5-6-extensions-with-xml-views-html-js-custom-controls/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
April 12, 2023 16 minute read

abap2UI5 – (5/7) Extensions with XML Views, HTML, JS & Custom Controls

0 7 808

Welcome to part five of this blog series introducing abap2UI5 — an open-source project for developing standalone UI5 apps in pure ABAP.

This post explains various ways of creating views and enhancing them with Custom Controls, HTML, CSS, JavaScript and third-party libraries.

Find all the information about the project on GitHub and stay up-to-date by following on Twitter.

Blog Series

There are different ways to create views in abap2UI5. To compare each way, we will begin by creating a simple view that includes an input field and a button, and then compare the creation processes. This is how the view looks like:

Simple%20View%20-%20Created%20in%20four%20three%20different%20ways

View created in three different ways

1. View Class Typed Way

First, we will create the view using the method explained in previous blog posts and use the class z2ui5_cl_xml_view. The code for creating the view looks like this:

lo_view->shell(
      )->page(
              title          = 'abap2UI5 - NORMAL NORMAL NORMAL'
              navbuttonpress = client->_event( 'BACK' )
              shownavbutton  = abap_true
          )->header_content(
              )->link(
                  text = 'Source_Code'
                  href = z2ui5_cl_xml_view_helper=>hlp_get_source_code_url( app = me get = client->get( ) )
                  target = '_blank'
          )->get_parent(
          )->simple_form( 'Form Title'
              )->content( 'form'
                  )->title( 'Input'
                  )->label( 'quantity'
                  )->input( client->_bind( quantity )
                  )->button(
                      text  = 'NORMAL'
                      press = client->_event( 'NORMAL' )
                  )->button(
                      text  = 'GENERIC'
                      press = client->_event( 'GENERIC' )
                     )->button(
                      text  = 'XML'
                      press = client->_event( 'XML' ) ).

The class z2ui5_cl_xml_view provides ABAP-typed methods for creating UI5 controls. The class acts as an ABAP-typed proxy for the SAPUI5 Library and as an result only contains repetitive code with no extra logic:

Defintion
Bildschirm%C2%ADfoto-2023-04-11-um-19.05.19.png

This class stores all control information in a tree structure and ultimately generates a stringified XML view based on that structure. This class is continuously updated with additional controls. If there is a control that you would like to use that is not currently part of the class, you can add it manually and submit a pull request on GitHub so that it becomes available for everyone to use.

Notably, every method in the class calls the _generic method to save the control information. As a result, we can skip the first step and call the untyped method directly. This will be the second approach for generating a view for abap2UI5, which we will explore now.

2. View Class Generic Way

To use a control that is not included in the view class, we can also use the method ‘_generic’. The generic approach for the above example looks as follows:

lo_view->_generic( 'Shell' )->_generic(
       name      = `Page`
       t_prop = VALUE #(
           ( n = `title`          v = 'abap2UI5 - GENERIC GENERIC GENERIC' )
           ( n = `showNavButton`  v = `true` )
           ( n = `navButtonPress` v = client->_event( 'BACK' ) ) )
       )->_generic(
            name = `SimpleForm`
            ns   = `form`
            t_prop = VALUE #(
                ( n = `title` v = 'title' )
       ) )->_generic(
            name = `content`
            ns   = `form`
       )->_generic(
            name = `Label`
            t_prop = VALUE #(
                ( n = `text` v = 'quantity' )
       ) )->get_parent( )->_generic(
            name = `Input`
            t_prop = VALUE #(
                ( n = `value` v = client->_bind( quantity ) )
       ) )->get_parent(
        )->_generic(
            name = `Button`
            t_prop = VALUE #(
                ( n = `text`  v = `NORMAL` )
                ( n = `press` v = client->_event( 'NORMAL' ) ) )
           )->get_parent(
           )->_generic(
            name = `Button`
            t_prop = VALUE #(
                ( n = `text`  v = `GENERIC` )
                ( n = `press` v = client->_event( 'GENERIC' ) ) )
            )->get_parent(
                 )->_generic(
            name = `Button`
            t_prop = VALUE #(
                ( n = `text`  v = `XML` )
                ( n = `press` v = client->_event( 'XML' ) ) ) ).

If you wish to use different namespaces, you have the option to modify them in the call of the factory method. The default namespaces are as follows:

Bildschirm%C2%ADfoto-2023-04-15-um-10.42.27.png

Default namespaces of the abap2UI5 view

This generic approach allows us to generate any control, but it requires more code to be written and we lose the benefits of typed interfaces that make the first approach more convenient to use.

Now, let us examine how the view is provided to abap2UI5. The view class creates the UI5 view and outputs it as a string, which is then received by abap2UI5 as a ready to use UI5-XML-View:

client->set_next( value #( xml_main = page->get_root( )->xml_get( ) ) ).

That means abap2UI5 is not involved in the creation of the views. It operates entirely independently of the view class and just receives the result and sends it to the frontend as it is.

Therefore you also have the freedom to build you own view class or encapsulate the existing one in a way that better fits your needs. The provided view class is just a suggestion (utility function) but there might be better solutions for the view creation process.

Moreover, we can bypass the z2ui5_cl_cml_view class now and use XML directly, which will be the next approach we will focus on.

3. XML Way

Now we copy the XML view directly into ABAP as a string and replace the event handler and data binding. The previous example in XML looks like this:

    data(lv_xml) = `<mvc:View controllerName="zzdummy" displayBlock="true" height="100%" xmlns:core="sap.ui.core" xmlns:l="sap.ui.layout" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:f="sap.ui.layout.form" xmlns:mvc="sap.ui.co` &&
`re.mvc" xmlns:editor="sap.ui.codeeditor" xmlns:ui="sap.ui.table" xmlns="sap.m" xmlns:uxap="sap.uxap" xmlns:mchart="sap.suite.ui.microchart" xmlns:z2ui5="z2ui5" xmlns:webc="sap.ui.webc.main" xmlns:text="sap.ui.richtexteditor" > <Shell> <Page ` && |\n|
&&
                          `  title="abap2UI5 - XML XML XML" ` && |\n|  &&
                          `  showNavButton="true" ` && |\n|  &&
                          `  navButtonPress="` &&  client->_event( 'BACK' ) && `" ` && |\n|  &&
                          ` > <headerContent ` && |\n|  &&
                          ` > <Link ` && |\n|  &&
                          `  text="Source_Code" ` && |\n|  &&
                          `  target="_blank" ` && |\n|  &&
                          `  href="<system>sap/bc/adt/oo/classes/Z2UI5_CL_APP_DEMO_23/source/main" ` && |\n|  &&
                          ` /></headerContent> <f:SimpleForm ` && |\n|  &&
                          `  title="Form Title" ` && |\n|  &&
                          ` > <f:content ` && |\n|  &&
                          ` > <Title ` && |\n|  &&
                          `  text="Input" ` && |\n|  &&
                          ` /> <Label ` && |\n|  &&
                          `  text="quantity" ` && |\n|  &&
                          ` /> <Input ` && |\n|  &&
                          `  value="` &&  client->_bind( quantity ) && `" ` && |\n|  &&
                          ` /> <Button ` && |\n|  &&
                          `  press="` &&  client->_event( 'NORMAL' ) && `"`  && |\n|  &&
                          `  text="NORMAL" ` && |\n|  &&
                          ` /> <Button ` && |\n|  &&
                              `  press="` &&  client->_event( 'GENERIC' ) && `"`  && |\n|  &&
                          `  text="GENERIC" ` && |\n|  &&
                          ` /> <Button ` && |\n|  &&
                             `  press="` &&  client->_event( 'XML' ) && `"`  && |\n|  &&
                          `  text="XML" ` && |\n|  &&
                          ` /></f:content></f:SimpleForm></Page></Shell></mvc:View>`.

abap2UI5 takes the XML view as it is (only changing the controller method) and sends it to the frontend.

The following demonstration showcases the three approaches in action. Each time, the view remains the same, but it is created using a different method:

app%20in%20three%20ways

App with views created in three different ways

Check out the source code here.

The XML approach may appear unconventional and could potentially be difficult to maintain. However, it is a speedy way to create prototypes, as this approach enables us to copy and paste any code snippets from the SAPUI5 Library.

Here is an example of how to copy the generic tile example into abap2UI5:

copy%20and%20paste%20view%20of%20ui5%20documentation

Copy View of the Documentation and run it with abap2UI5

Here we add a popup using the same method:

add%20popup

Copy Popup of the UI5 Documentation and run it with abap2UI5

We add interaction by replacing the event handler with the abap2UI5 event handler:

  `<Button text="BACK" type="Emphasized" press="` && client->_event( 'BACK') && `"/>`

We add data transfer by replacing the data binding with the abap2UI5 binding:

 ` <Input id="loadingMinSeconds" width="8rem" type="Number" description="seconds" value="` && client->_bind( mv_value ) && `"/>`

Within minutes, we can create a fully functional prototype with event handling and data transfer:

working%20copy%20and%20paste%20app

UI5-XML-View in abap2UI5 with Events and Data Transfer

Check out the source code of the app here.

Although the video makes it seem easy, it is important to exercise caution when using this approach. You are now fully responsible for everything. abap2UI5 simply sends it as is to the frontend, so you must ensure that the namespace is correct, libraries can be loaded, images are displayed properly and so on. This approach provides flexibility, but also comes with the potential for problems to occur.

If you need to make corrections, you can activate logging, and the XML view output will be written to the console of your browser. From there, you can copy and adjust it and copy it back into the ABAP class.

debugger%20to%20copy%20the%20xml

Console Output of the UI5-XML-View

A great way to create views before copying them into abap2UI5 is to use sandboxes, for example use the OpenUI5 Sandbox:

Bildschirm%C2%ADfoto-2023-04-12-um-09.38.00.png

OpenUI5 SandBox

After we now got full control of the view creation, we can go one step further and add normal HTML, CSS, and JS.

4. HTML, CSS & JavaScript

UI5 provides the ability to use native HTML in XML Views. You can refer to this documentation for more information. A simple example of HTML output in XML Views looks like this:

Bildschirm%C2%ADfoto-2023-04-11-um-19.24.27.png

Native HTML and CSS in abap2UI5

Here is the XML view in ABAP for the above example (and a more readable version):

    app-next-xml_main = `<mvc:View controllerName="project1.controller.View1"` && |\n|  &&
                          `    xmlns:mvc="sap.ui.core.mvc" displayBlock="true"` && |\n|  &&
                          `  xmlns:z2ui5="z2ui5"  xmlns:m="sap.m" xmlns="http://www.w3.org/1999/xhtml"` && |\n|  &&
                          `    ><m:Button ` && |\n|  &&
                          `  text="back" ` && |\n|  &&
                          `  press="` && client->_event( 'BACK' ) && `" ` && |\n|  &&
                          `  class="sapUiContentPadding sapUiResponsivePadding--content"/> ` && |\n|  &&
                   `       <m:Link target="_blank" text="Source_Code" href="` && z2ui5_cl_xml_view_helper=>hlp_get_source_code_url( app = me get = client->get( ) ) && `"/>` && |\n|  &&
                          `<html><head><style>` && |\n|  &&
                          `body {background-color: powderblue;}` && |\n|  &&
                          `h1   {color: blue;}` && |\n|  &&
                          `p    {color: red;}` && |\n|  &&
                          `</style>` &&
                          `</head>` && |\n|  &&
                          `<body>` && |\n|  &&
                          `<h1>This is a heading with css</h1>` && |\n|  &&
                          `<p>This is a paragraph with css.</p>` && |\n|  &&
                          `<h1>My First JavaScript</h1>` && |\n|  &&
                          `<button type="button">send</button>` && |\n|  &&
                          `<Input id='input' value='frontend data' /> ` &&
                          `</body>` && |\n|  &&
                          `</html> ` && |\n|  &&
                            `</mvc:View>`.
<mvc:View controllerName="z2ui5_controller"
	xmlns:mvc="sap.ui.core.mvc" displayBlock="true"
	xmlns:z2ui5="z2ui5"
	xmlns:m="sap.m"
	xmlns="http://www.w3.org/1999/xhtml"
    >
	<m:Button 
  text="back" 
  press="onEvent( { 'EVENT' : 'BACK', 'METHOD' : 'UPDATE' } )" 
  class="sapUiContentPadding sapUiResponsivePadding--content"/>
	<m:Link target="_blank" text="Source_Code" href="https://6654aaf7-905f-48ea-b013-3811c03fcba8.abap-web.us10.hana.ondemand.com/sap/bc/adt/oo/classes/Z2UI5_CL_APP_DEMO_32/source/main"/>
	<html>
		<head>
			<style>
body {background-color: powderblue;}
h1   {color: blue;}
p    {color: red;}
</style>
		</head>
		<body>
			<h1>This is a heading with css</h1>
			<p>This is a paragraph with css.</p>
			<h1>My First JavaScript</h1>
			<button type="button">send</button>
			<Input id='input' value='frontend data' />
		</body>
	</html>
</mvc:View>

To enable interaction and data transfer, we add JavaScript to the XML view:

  app-next-xml_main = `<mvc:View controllerName="project1.controller.View1"` && |\n|  &&
                          `    xmlns:mvc="sap.ui.core.mvc" displayBlock="true"` && |\n|  &&
                          `  xmlns:z2ui5="z2ui5"  xmlns:m="sap.m" xmlns="http://www.w3.org/1999/xhtml"` && |\n|  &&
                          `    ><m:Button ` && |\n|  &&
                          `  text="back" ` && |\n|  &&
                          `  press="` && client->_event( 'BACK' ) && `" ` && |\n|  &&
                          `  class="sapUiContentPadding sapUiResponsivePadding--content"/> ` && |\n|  &&
                   `       <m:Link target="_blank" text="Source_Code" href="` && z2ui5_cl_xml_view_helper=>hlp_get_source_code_url( app = me get = client->get( ) ) && `"/>` && |\n|  &&
                          `<html><head><style>` && |\n|  &&
                          `body {background-color: powderblue;}` && |\n|  &&
                          `h1   {color: blue;}` && |\n|  &&
                          `p    {color: red;}` && |\n|  &&
                          `</style>` &&
                          `</head>` && |\n|  &&
                          `<body>` && |\n|  &&
                          `<h1>This is a heading with css</h1>` && |\n|  &&
                          `<p>This is a paragraph with css.</p>` && |\n|  &&
                          `<h1>My First JavaScript</h1>` && |\n|  &&
                          `<button onclick="myFunction()" type="button">send</button>` && |\n|  &&
                          `<Input id='input' value='frontend data' /> ` &&
                          `<script> function myFunction( ) { sap.z2ui5.oView.getController().onEvent({ 'EVENT' : 'POST', 'METHOD' : 'UPDATE' }, document.getElementById(sap.z2ui5.oView.createId( "input" )).value ) } </script>` && |\n|  &&
                          `</body>` && |\n|  &&
                          `</html> ` && |\n|  &&
                            `</mvc:View>`.

Finally, we have a working frontend application that uses events and data transfer to the backend with HTML, CSS and JS:

file%20import%20from%20trontend%20into%20abaop2ui5%20backend

HTML, CSS and JavaScript in abap2UI5

Here we create the server roundtrip with abap2UI5 and use the second parameter of the event method to transfer data to the backend:

sap.z2ui5.oView.getController().onEvent({ 'EVENT' : 'POST', 'METHOD' : 'UPDATE' }, document.getElementById(sap.z2ui5.oView.createId( "input" )).value ) 

Then in the backend the data can be found in the following parameter:

client->popup_message_toast( app-get-event_data ).

The value of ‘app-get-event_data’ can also be filled with a stringified JSON, which provides a generic approach for sending data to the backend without requiring changes to the HTTP handler.

Take a look at the full source code of the app here.

This approach provides a lot of freedom, but it also requires you to manage JavaScript, CSS, and HTML within your normal ABAP code. Good wrappers can be helpful, but it’s worth considering whether it would be better to create a regular frontend app instead.

Additionally, the code in this example isn’t considered good practice because we’re calling UI5 from the outside, using global variables etc. So, consider it only as an example to see what is possible, and not as a coding guideline to follow.

Example – Canvas & SVG

We can now use this approach for functionalities that are not normally within the scope of UI5. For example, we can use canvas and SVG:

canvas%20and%20svg

Canvas and SVG in abap2UI5

You can find the source code here.

Example – Include third-party Libraries

Or including third-party open-source libraries — for example use JSBarcode to display barcodes:

JSBarcode%20inclued

JSBarcode in abap2UI5

The source code of the app is here.

With this approach, it is also possible to include additional libraries in the future to, for example, display QR codes, use barcode scanning with the camera, utilize localization or leverage other device capabilities. Per default to prevent cross-side-scripting loading third party libraries is not allowed. So if you include other external libraries, make also sure to adjust the csp meta tag as described at security here.

Now let’s move on to the final part of this blog post and take a look at UI5 custom controls, which are a way to encapsulate HTML CSS, and JS for the use with UI5.

5. Custom Control Way

Custom controls are the recommended way to build extensions in UI5. A simple example with a button and data transfer looks like this:

cc%20control

Custom Control in abap2UI5

The source code is here.

JavaScript of the custom control:

sap.ui.define( [
        "sap/ui/core/Control",
    ], function (Control) {
        "use strict";
        return Control.extend("z2ui5.MyCC", {
            metadata: {
                properties: {
                    value: { type: "string" }
                },
                events: {
                    "change": {
                        allowPreventDefault: true,
                        parameters: {}
                    }
                }
            },
            renderer: function (oRm, oControl) {
                oControl.oInput = new sap.m.Input({
                    value: oControl.getProperty("value")
                });
                oControl.oButton = new sap.m.Button({
                    text: 'button text',
                    press: function (oEvent) {
                        debugger;
                        this.setProperty("value",  this.oInput.getProperty( 'value')  )
                        this.fireChange();
                    }.bind(oControl)
                });
                oRm.renderControl(oControl.oInput);
                oRm.renderControl(oControl.oButton);
            }
    });
});

Embedded in abap2UI5 as a string for the XML view:

 `<script>if(!z2ui5.MyCC){ jQuery.sap.declare("z2ui5.MyCC");` && |\n|  &&
                            `    sap.ui.define( [` && |\n|  &&
                            `        "sap/ui/core/Control",` && |\n|  &&
                            `    ], function (Control) {` && |\n|  &&
                            `        "use strict";` && |\n|  &&
                            `        return Control.extend("z2ui5.MyCC", {` && |\n|  &&
                            `            metadata: {` && |\n|  &&
                            `                properties: {` && |\n|  &&
                            `                    value: { type: "string" }` && |\n|  &&
                            `                },` && |\n|  &&
                            `                events: {` && |\n|  &&
                            `                    "change": {` && |\n|  &&
                            `                        allowPreventDefault: true,` && |\n|  &&
                            `                        parameters: {}` && |\n|  &&
                            `                    }` && |\n|  &&
                            `                }` && |\n|  &&
                            `            },` && |\n|  &&
                            `            renderer: function (oRm, oControl) {` && |\n|  &&
                            `                oControl.oInput = new sap.m.Input({` && |\n|  &&
                            `                    value: oControl.getProperty("value")` && |\n|  &&
                            `                });` && |\n|  &&
                            `                oControl.oButton = new sap.m.Button({` && |\n|  &&
                            `                    text: 'button text',` && |\n|  &&
                            `                    press: function (oEvent) {` && |\n|  &&
                            `                        this.setProperty("value", this.oInput.getProperty( 'value')  )` && |\n|  &&
                            `                        this.fireChange();` && |\n|  &&
                            `                    }.bind(oControl)` && |\n|  &&
                            `                });` && |\n|  &&
                           `                oRm.renderControl(oControl.oInput);` && |\n|  &&
                            `                oRm.renderControl(oControl.oButton);` && |\n|  &&
                            `            }` && |\n|  &&
                            `    });` && |\n|  &&
                            `}); } </script>`

To use the custom control, we need to load its JS snippet before rendering it on a view. Therefore, we send it with the first button click. After that, we can use the custom control in the second and all following requests. The usage of the custom control in abap2UI5 is as follows:

` <z2ui5:MyCC change=" ` && client->_event( 'MYCC' ) && `"  value="` && client->_bind( mv_value ) && `"/>`

The custom control encapsulation makes data transfer very easy. It is integrated into the XML Views and can be easily bound with abap2UI5. In addition, nothing is called from the outside anymore, and we stay completely within the UI5 logic.

It becomes even more convenient when we additionally add the custom control to the view class from the beginning. Take a look at this example of how we use the custom control of the file uploader:

page->zz_file_uploader(
     value       = client->_bind( mv_value )
     path        = client->_bind( mv_path )
     placeholder = 'filepath here...'
     upload      = client->_event( 'UPLOAD' ) ).

It has a completely ABAP-typed interface with this. Here is the source code of the app.

The next step would be to enhance this custom control with the JavaScript logic we saw before (such as Canvas, SVG, and third-party libraries).

Conclusion

As we have seen, there are many different ways to create views in abap2UI5, each with its own level of flexibility and maintenance requirements. The view creation process is independent of abap2UI5, as abap2UI5 simply takes the created view and sends it to the frontend as is. Therefore, each user can choose the method that works best for them.

The view class-based approach is very intuitive and is built on typed ABAP methods. The XML approach, which involves copying and pasting views, is extremely fast for creating prototypes with popups in a short amount of time, but maintaining the views can be challenging later on. The last approaches offer a lot of flexibility, but they require deep knowledge of JS and HTML and essentially use the ABAP source code as a BSP. It’s a question whether frontend development may be better in this case.

The idea behind abap2UI5 is to just provide a basic layer that offers some flow logic (for easy switching between apps) and server-client communication (for creating view models, handling events, and transferring data). However, the entire view logic remains outside of the framework. Therefore, the class z2ui5_cl_xml_view and every demo shown in this blog post are only part of the demo section and not part of the abap2UI5 functionality. With this approach abap2UI5 remains small and flexible, suitable for a wide range of use cases.

Feel free to find your best way to create views and extend abap2UI5!

Summary

This was part five of this introduction to abap2UI5. You now have an understanding about the different ways of creating views with abap2UI5 and some ideas how to extend it with HTML, CSS and JavaScript.

In the next blog post, we will focus on how to install, configure and debug abap2UI5.

Thank you for reading this blog post!

Your questions, comments and wishes for this project are always welcome, create an issue or leave a comment.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK