rogerxu / rogerxu.github.io

Roger Xu's Blog
3 stars 2 forks source link

SAPUI5 #24

Open rogerxu opened 7 years ago

rogerxu commented 7 years ago

SAPUI5 SDK - Demo Kit v2.0

Essentials

rogerxu commented 7 years ago

Resources

SAPUI5 - SAP Jam SAPUI5 News - SAP Jam

rogerxu commented 7 years ago

CI

Continuous Integration (CI) Best Practices with SAP: Introduction and Navigator | SAP

CI Best Practices Guide: SAPUI5/SAP Fiori on SAP Cloud Platform | SAP

rogerxu commented 6 years ago

Reuse

Custom Library

SAPUI5 Custom control library: Web IDE Development, deployment to HCP, and to on-premise ABAP Repository. Part 1. | SAP Blogs

SAPUI5 deploy custom control library to ABAP repository using Grunt | SAP Blogs

openui5/controllibraries.md at master · SAP/openui5

Component Reuse

  1. Demystifying the art of component reuse in SAPUI5 | SAP Blogs
  2. Component Reuse – 4 ways of sharing data between the apps | SAP Blogs
  3. Component Reuse – The move to Manifest.json | SAP Blogs
rogerxu commented 6 years ago

QUnit

Unit Test

Creating a QUnit Test Page

sap/ui/qunit/QUnitUtils - https://sapui5.hana.ondemand.com/resources/sap/ui/qunit/QUnitUtils-dbg.js

unitTests.qunit.html

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta charset="utf-8">
    <title>Unit Tests</title>

    <script id="sap-ui-bootstrap"
      src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
      data-sap-ui-libs="sap.m, sap.ui.layout, sap.ushell, sap.ui.comp, sap.ui.generic.app"
      data-sap-ui-preload="async"
      data-sap-ui-theme="sap_belize"
      data-sap-ui-compatVersion="edge"
      data-sap-ui-frameOptions="deny"
      data-sap-ui-resourceRoots='{
        "com/my/app": "../../",
        "test/unit": "./"
      }'>
    </script>

    <link rel="stylesheet" href="qunit-fix.css">
    <script src="qunitTest.js"></script>
  </head>
  <body>
    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
  </body>
</html>

qunitTest.js

jQuery.sap.require("sap.ui.qunit.qunit-css");
jQuery.sap.require("sap.ui.thirdparty.qunit");
jQuery.sap.require("sap.ui.qunit.qunit-junit");
jQuery.sap.require("sap.ui.qunit.qunit-coverage");

QUnit.config.autostart = false;

sap.ui.getCore().attachInit(function() {
  "use strict";

  sap.ui.require([
    "sap/ui/thirdparty/sinon"
  ], function() {
    sap.ui.require([
      "test/unit/allTests"
    ], function() {
      QUnit.start();
    });
  });
});

qunit-fix.css

/* override ushell style to show scrollbar */
html.sap-desktop {
  overflow: auto;
}

allTests.js

sap.ui.require([
  "test/unit/Component",
  "test/unit/controller/test",
  "test/unit/view/test",
  "test/unit/util/test"
], function() {
  "use strict";
});

Test Suite

testsuite.qunit.html

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta charset="UTF-8">
  <title>QUnit TestSuite</title>

  <script src="../resources/sap/ui/qunit/qunit-redirect.js"></script>
  <script>
    /**
     * Add test pages to this test suite function.
     *
     * @return {parent.jsUnitTestSuite} The test suite object.
     */
    function suite() {
      "use strict";

      var suite = new parent.jsUnitTestSuite();
      var contextPath = window.location.pathname.substring(0, window.location.pathname.lastIndexOf("/") + 1);
      suite.addTestPage(contextPath + "unit/unitTests.qunit.html");
      //suite.addTestPage(contextPath + "integration/opaTests.qunit.html");

      return suite;
    }
  </script>
</head>
<body>
</body>
</html>
rogerxu commented 6 years ago

Bootstrapping

Bootstrapping: Loading and Initializing

Bootstrap script

<script id="sap-ui-bootstrap"
    type="text/javascript"
    src="https://sapui5.hana.ondemand.com/1.75.0/resources/sap-ui-core.js"
    data-sap-ui-theme="sap_fiori_3"
    data-sap-ui-libs="sap.m,sap.ushell"
    data-sap-ui-compatVersion="edge"
    data-sap-ui-async="true"
    data-sap-ui-onInit="module:my/app/main"
    data-sap-ui-resourceRoots='
        "my.app": "../"
    '>
</script>

Configuration

Configuration of the SAPUI5 Runtime

Global Configuration Objects

As the configuration is evaluated during bootstrap, the configuration object must be created before SAPUI5 is bootstrapped.

<script type="text/javascript">
window["sap-ui-config"] = {
  theme: "sap_belize",
  libs: "sap.m",
};
</script>
<script id="sap-ui-bootstrap"
  src="resources/sap-ui-core.js">
</script>

Configuration Options

Configuration Options and URL Parameters

Option Type Comment
debug false, sap/ui/model/odata/v2/, sap/ui/model/**/v2/, sap/ui/model/* sap/ui/model/odata/v2/ will load all debug sources for all modules of the OData V2 model.
frameOptions allow, deny, trusted
language en-US
libs [] ['sap.m', 'sap.ushell']
logLevel NONE, FATAL, ERROR, WARNING, INFO, DEBUG, ALL
modules []
noConflict false, true Recommend to use the noConflict=true
onInit string module:my/app/main/Module
async false, true Recommend to use the async=true configuration parameter
resourceRoots object {'com/my/webapp': '../'}
theme base, sap_fiori_3
rogerxu commented 6 years ago

Components and Descriptor

Component

Components are loaded and created via the component factory function sap.ui.component. You can either pass the name of the component or the URL of the descriptor file (manifest.json) to load it via the descr iptor. We recommend loading the component using the descriptor (if available) - it improves performance during the initial load since the loading process can be parallelized and optimized.

A component is organized in a unique namespace, the namespace of the component equals the component name.

The Component.js file is the component controller and provides the runtime metadata and the component methods.

sap.ui.define(["sap/ui/core/UIComponent"], function(UIComponent) {
  "use strict";

  var Component = UIComponent.extend("samples.components.sample.Component", {
    metadata: {
      manifest: "json"
    },

    init: function() {
      UIComponent.prototype.init.apply(this, arguments);

      // The routing instance needs to be initialized here
      this.getRouter().initialize();
    },

    createContent: function() {
      // By default, the UI component creates the `sap.ui5/rootView` declared in the manifest as the root control
    }
  });

  return Component;
});

Component Containers

The ComponentContainer control wraps a UI component. You use the ComponentContainer control in the SAPUI5 control tree in the same way as any other control.

Load the component asynchronously in "manifest first" mode by specifying the component name:

var oContainer = new ComponentContainer({
  name: "samples.components.sample",
  manifest: true,
  async: true
});

oContainer.placeAt("target");

Reuse Components

manifest.json

{
  "sap.ui5": {
    "componentUsages": {
      "myreuse": {
        "name": "sap.reuse.component",
        "lazy": false
      }
    }
  }
}
this.createComponent("myreuse").then(function(oComponent) {
  // ...
});
<View>
    <ComponentContainer usage="myreuse" async="true"></ComponentContainer>
</View>

Declarative API for Initial Components

<script id="sap-ui-bootstrap"
    src="/resources/sap-ui-core.js"
    data-sap-ui-onInit="module:sap/ui/core/ComponentSupport">
</script>

This module scans the DOM for HTML elements containing a special data attribute named data-sap-ui-component. All DOM elements marked with this data attribute will be regarded as container elements into which a sap/ui/core/ComponentContainer is inserted.

<body id="content" class="sapUiBody">
    <div data-sap-ui-component
        data-id="container"
        data-name="sap.ui.core.samples.formatting"
        data-handle-validation="true">
    </div>
</body>

App Descriptor

Descriptor for Applications, Components, and Libraries

At runtime, the manifest.json content can be accessed from the component via the component metadata:

// get the component class
sap.ui.require(['sap/samples/Component'], function(SampleComponent) {

  // getting complete manifest from component metadata
  var manifest = SampleComponent.getMetadata().getManifest();

  // or getting a namespace
  var appInfo = SampleComponent.getMetadata().getManifestEntry("sap.app");
  var id = SampleComponent.getMetadata().getManifestEntry("/sap.app/id");

  // sap.ui.core.Manifest
  var manifestObject = SampleComponent.getMetadata().getManifestObject();
});
rogerxu commented 6 years ago

App

Overview

App Overview: The Basic Files of Your App

App Initialization

App Initialization: What Happens When an App Is Started?

Project Structure

Folder Structure: Where to Put Your Files

|-- <root>
  |-- scripts
    |-- fsext.js
    |-- postinstall.js
  |-- webapp
    |-- controller
      |-- BaseController.js
      |-- ErrorHandler.js
      |-- NotFound.controller.js
      |-- Worklist.controller.js
    |-- i18n
      |-- i18n.properties
    |-- localService
      |-- mockdata
      |-- annotations.xml
      |-- metadata.xml
      |-- mockserver.js
    |-- model
      |-- type
        |-- ISODateTime.js
      |-- formatter.js
    |-- test
      |-- integration
        |-- pages
        |-- AllJourneys.js
        |-- opaTests.qunit.html
      |-- unit
        |-- controller
        |-- util
        |-- Component.js
        |-- unitTests.qunit.html
      |-- flpSandbox.html
      |-- flpSandbox.js
      |-- flpSandboxMockServer.html
      |-- initFlpSandbox.js
      |-- initFlpSandboxMockServer.js
      |-- testsuite.qunit.html
      |-- testsuite.qunit.js
    |-- util
      |-- AsyncUtils.js
      |-- ServiceUtils.js
    |-- vendor
    |-- view
      |-- fragments
        |-- DiscardPopover.fragment.xml
        |-- ProductDialog.fragment.xml
      |-- App.view.xml
      |-- NotFound.view.xml
      |-- Worklist.view.xml
    |-- Component.js
    |-- manifest.json
  |-- .env
  |-- .eslintignore
  |-- .eslintrc
  |-- Gruntfile.js
  |-- karma-ci.conf.js
  |-- karma.conf.js
  |-- neo-app.json
  |-- package-lock.json
  |-- package.json
  |-- server.js
  |-- whitesource.config.json
  |-- xs-app.json
rogerxu commented 6 years ago

Modules and Dependencies

Modules and Dependencies

Define a Module

sap.ui.define supports relative path.

sap.ui.define([
  "jquery.sap.global",
  "sap/ui/core/mvc/Controller",
  "sap/ui/model/json/JSONModel",
  "../util/VersionUtil",
], function(jQuery, Controller, JSONModel, VersionUtil) {
  "use strict";

  return Controller.extend("com.my.app.controller.Worklist", {
    onInit: function() {
    },

    onSavePress: function () {
      // dynamically load a dialog once it is needed
      sap.ui.require(["sap/m/Dialog"], function(Dialog) {
        var oDialog = new Dialog();
        oDialog.open();
      });
    }
  });
});

Load Module

Loading a Module

Asynchronous Loading

sap.ui.require does not support relative path.

sap.ui.require([
  "jquery.sap.global",
  "com/my/app/util/VersionUtil"
], function(jQuery, VersionUtil) {
  "use strict";
});

Synchronous Retrieval of a Single Module Value

When calling sap.ui.require with a single string as argument, the respective module has to be loaded already.

If the module is not yet loaded or it is not a SAPUI5 module (third-party module), the return value is undefined.

// If JSONModel class is loaded, it is returned. If the module is not loaded yet, there will be no additional loading request.
// The variable JSONModel might be undefined after making this call.
var JSONModel = sap.ui.require("sap/ui/model/json/JSONModel");
if (JSONModel === undefined) {
  console.log('This module is not loaded');
}

Module Location

Multiple Module Locations

sap.ui.loader.config({
    paths: {
        "my/module": "https://example.com/resources/my/module"
    }
});

Adapting to the Modularization of the Core

image

jQuery.sap.registerModulePath

Registers a URL prefix for a module name prefix.

jQuery.sap.registerModulePath("com.my.webapp", "/my-webapp/resources/my/webapp/");

jQuery.sap.registerResourcePath

Registers a URL prefix for a resource name prefix.

jQuery.sap.registerResourcePath("com/my/webapp", "/my-webapp/resources/my/webapp/");

sap.ui.localResources

Redirects access to resources that are part of the given namespace to a location relative to the assumed application root folder.

jQuery.sap.localResources("com.my.webapp");

It is intended to make this configuration obsolete in future releases, but for the time being, applications must call this method when they want to store resources relative to the assumed application root folder.

rogerxu commented 4 years ago

MVC

Model View Controller (MVC)

Models

image

Views

XML View

<mvc:View
     xmlns:mvc="sap.ui.core.mvc"
     xmlns:layout="sap.ui.layout"
     xmlns:form="sap.ui.layout.form"
     xmlns="sap.m">
</mvc:View>

Handling Events in XML Views

<Button core:require="{Helper:'path/to/Helper'}" text="Press Me" press="Helper.doSomething.call($controller, 'Hello World')"/>
<Button text="Press Me" press=".doSomething($event)"/>
<Button text="Press Me" press=".doSomething($controller)"/>

Require Modules in XML View and Fragment

<mvc:View controllerName="some.Controller" xmlns="sap.m"
      xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc"
      core:require="{Util:'some/Util'}">
  <Panel>
      <Image src="http://www.sap.com/global/ui/images/global/sap-logo.png"/>
      <Text text="{formatter: 'Util.format', path: '/text'}"/>
      <List core:require="{
              Factory:'some/Factory'
          }" id="list" items="{path:'/items', factory:'Factory.createItem'}">
      </List>
   </Panel>
</mvc:View>

Instantiating Views

Asynchronous Mode

XMLView.create({
    viewName: "my.own.view"
}).then(function(oView) {
    // the instance is available in the callback function
    oView.placeAt("uiArea");
});

Synchronous Mode

We do not recommend this mode. Use the asynchronous mode instead.

var oController = sap.ui.controller("my.own.controller");
var oView = sap.ui.view({
    viewName: "my.own.view",
    controller: " my.own.controller",
    type: sap.ui.core.mvc.ViewType.XML
});

// the instance is available now
oView.placeAt("uiArea");

Controller

Lifecycle Hooks

Methods Section in the Controller Metadata

By default, all methods that do not start with an underscore or with prefix "on", "init" or "exit" are public. You can get all public methods of a controller by using the oController.getMetadata().getPublicMethods() API.

Using Controller Extension

All public methods need to stay compatible:

sap.ui.define(['sap/ui/core/mvc/ControllerExtension', 'sap/ui/core/mvc/OverrideExecution'], function(ControllerExtension, OverrideExecution) {
  "use strict";
  return ControllerExtension.extend("my.extension.SampleExtension", {
    metadata: {
      // extension can declare the public methods
      // in general methods that start with "_" are private
      methods: {
        publicMethod: {
          public: true /*default*/ ,
          final: false /*default*/ ,
          overrideExecution: OverrideExecution.Instead /*default*/
        },
        finalMethod: {
          final: true
        },
        onMyHook: {
          public: true /*default*/ ,
          final: false /*default*/ ,
          overrideExecution: OverrideExecution.After
        },
        couldBePrivate: {
          public: false
        }
      }
    },

    // adding a private method, only accessible from this controller extension
    _privateMethod: function() {},
    // adding a public method, might be called from or overridden by other controller extensions as well
    publicMethod: function() {},
    // adding final public method, might be called from, but not overridden by other controller extensions as well
    finalMethod: function() {},
    // adding a hook method, might be called from but not overridden by other controller extensions
    // overriding these method does not replace the implementation, but executes after the original method
    onMyHook: function() {},
    // method public by default, but made private via metadata
    couldBePrivate: function() {},
    // this section allows to extend lifecycle hooks or override public methods of the base controller
    override: {
      // override onInit of base controller
      onInit: function() {},
      // override public method of the base controller
      basePublicMethod: function() {}
    }
  });
});

Unique IDs

If the ID is created during the instantiation of the control, it is unique by default.

If you create further controls during runtime, the controller creates a unique ID by calling the oController.createId("someId") method. These methods add the necessary prefix to the ID.

Both child view IDs have the prefix myContainerView--:

The button view has the following IDs:

To get the button control, use the following code:

var oButton = oButtonView1.byId("aButton");

Nested Views

Initialize Views

Display First Nested View

Route Matched

Switch to Second Nested View

Switch to First Nested View

Routing

rogerxu commented 4 years ago

Data Binding

Data Binding

Binding Modes

Binding Types

image

Property Binding

You can use data-sap-ui-compatVersion="edge" to enable complex bindings.

<mvc:View
    xmlns="sap.m"
    xmlns:mvc="sap.ui.core.mvc">
    <Input
        value="{
            path:'/company/name',
            mode: 'sap.ui.model.BindingMode.OneWay'
        }"
    />
</mvc:View>

Data types can be used to parse user input in addition to formatting values. Parameter values are declared as formatOptions. Permitted formatOptions are properties of the corresponding data type.

<mvc:View
   controllerName="sap.ui.sample.App"
   xmlns="sap.m"
   xmlns:mvc="sap.ui.core.mvc">
   <Input
      value="{ path:'/company/revenue', 
           type: 'sap.ui.model.type.Float',
           formatOptions: {
                   minFractionDigits: 2,
                   maxFractionDigits: 2
           }
      }"/>
</mvc:View>

Context Binding

<mvc:View
    controllerName="sap.ui.sample.App"
    xmlns="sap.m"
    xmlns:mvc="sap.ui.core.mvc">
    <Input id="companyInput"
        binding="{/company}"
        value="{name}"
        tooltip="The name of the company is '{name}'"/> 
</mvc:View>

To define an element binding in JavaScript, for example in a controller, use the bindElement method on a control:

var oInput = this.byId("companyInput")
oInput.bindElement("/company");
oInput.bindProperty("value", "name");

List Binding

<mvc:View
    controllerName="sap.ui.sample.App"
    xmlns="sap.m"
    xmlns:l="sap.ui.layout"
    xmlns:mvc="sap.ui.core.mvc">        
    <List items="{
        path: '/companies',
        templateShareable: false
        sorter: {path: 'county', descending: false, group: '.getCounty'}, // single sorter
        sorter: [ // multiple sorters
            {path: 'county', descending: false},
            {path: 'city', descending: false}
        ],
        groupHeaderFactory: '.getGroupHeader',
        filters: [
            {path: 'city', operator: 'StartsWith', value1: 'B'},
            {path: 'revenue', operator: 'LT', value1: 150000000}
        ]}">
        <items>
            <StandardListItem
                title="{name}"
                description="{city}"
            />
        </items>
    </List> 
</mvc:View>
Initial Sorting, Grouping and Filtering for List Binding
sap.ui.define([
    "sap/ui/core/mvc/Controller",
    "sap/ui/model/json/JSONModel",
    "sap/m/GroupHeaderListItem "
], function (Controller, JSONModel, GroupHeaderListItem) {
    "use strict";
    return Controller.extend("sap.ui.sample.App", {
        getCounty: function(oContext) {
            return oContext.getProperty('county');
        },

        getGroupHeader: function(oGroup) {
            return new GroupHeaderListItem({
                title : oGroup.key
            }
        );
    },   
});

Manual Sorting and Filtering for List Binding

// manual sorting
oList.getBinding("items").sort(oSorter);

// manual filtering
oList.getBinding("items").filter([oFilterCity, oFilterRevenue]);
Lifecycle of Binding Templates

The lifecycle of the binding templates differs from the lifecycle of controls that are contained in an aggregation. Whenever a control object is destroyed, any aggregating object is destroyed as well. For list binding templates, you specify the behavior by using the additional property templateShareable in the parameter of the bindAggregation method of class sap.ui.base.ManagedObject.

To leave the parameter undefined is very error-prone, therefore we don't recommend this! Always set the parameter explicitly to true or false.

Extended Change Detection

The algorithm is implemented in the utility method jQuery.sap.arraySymbolDiff.

If you want to access the current context of a list binding, you should use getCurrentContexts in your app instead.

When a ListBinding is firing a Refresh event, the call to getContexts caused by this event is used to inform the ListBinding on the startIndex and length of entries requested by the control. No difference calculation is done on this specific call, as controls do not use the result of this call but instead wait for the data returned by the server.

For all other models (like a JSON model), you have to define the keys either by using a key property or by using a function that calculates the key in the binding info of their list binding as in the following example:

key property

oControl.bindItems({
  path: "/listData",
  key: "id"
});

key function

oControl.bindItems({
  path: "/listData",
  key: function(oContext) {
    return oContext.getProperty("user") + oContext.getProperty("timestamp"); 
  }
});

Control Development

Extended change detection is disabled by default. You activate extended change detection for your control by setting the bUseExtendedChangeDetection property either on the control prototype or a specific control instance. The ManagedObject class takes care of reading and applying the information about the differences to aggregations with the enableExtendedChangeDetection method.

https://sapui5.hana.ondemand.com/#/api/sap.ui.model.ListBinding%23methods/enableExtendedChangeDetection https://sapui5.hana.ondemand.com/#/api/sap.ui.model.ListBinding%23methods/getContexts

var binding = list.getBinding("items");
binding.enableExtendedChangeDetection(true, "id");
// var contexts = binding.getContexts(); // do not call this
var contexts = binding.getCurrentContexts();

Binding Syntax

Composite Binding

<TextField value="{
    parts: [
        {path:'birthday/day'},
        {path:'birthday/month'},
        {path:'birthday/year'}
    ], 
    formatter:'my.globalFormatter'
}"/>

Expression Binding

You can embed values from the model layer into an expression as additional bindings by using one of the following syntaxes:

<ObjectStatus state=="{= ${products>UnitPrice}  > ${/priceThreshold} ? 'Error' : 'Success' }"/>

To embed a path containing a closing curly brace into an expression binding, use a complex binding syntax: ${path:'...'}, for example

<Text visible="{:= ${path:'target>extensions/[${name} === \'semantics\']/value'} === 'email'}"/>

format i18n message

successMsg=Message is available from {0} until {1}
<mvc:View controllerName="sample.App" xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">
   <Text text="{= ${/data/message}.length &lt; 20
      ? ${i18n>errorMsg} 
      : ${parts: [
         {path: 'i18n>successMsg'},
         {path: '/data/today', type:'sap.ui.model.type.Date', constraints:{displayFormat:'Date'}},
         {path: '/data/tomorrow', type:'sap.ui.model.type.Date', constraints:{displayFormat:'Date'}}
      ], formatter: '.formatMessage'}}" />

</mvc:View>

Property Metadata Binding

The path to metadata must start with /#.

<Input maxLength="{/#Company/ZipCode/@maxLength}"/>
var myLabel = new sap.m.Label({text:"{/Companies(1)/CompanyCode/#@sap:label}"});
var myLabel2 = new sap.m.Label({text:"{City/#@sap:label}"});
myLabel2.bindElement("Companies(1)");

Formatting, Parsing, and Validating Data

We recommend to use a separate formatter.js file that groups the formatters and makes them globally available in your app. You can then load the formatters in any controller by defining a dependency and instantiating the formatter file in a formatter variable.

<Text text="{
    path : 'person/name',
    formatter : '.formatter.formatPersonName'
}" />

All data types inherit from the abstract sap.ui.model.Type class. A subclass of this class is sap.ui.model.SimpleType. The currently available types inherit from SimpleType class.

For simple data types, you can generate the following parameters in the constructor:

<Input value="{
    path: '/number',
    type: 'sap.ui.model.type.Integer',
    formatOptions: {
        minIntegerDigits: 3
    },
    constraints: {
        maximum: 1000
    }
}" />

Data Types

Predefined data types also offer visual feedback for erroneous user input. To turn this feature on, add the following line to your controller's init function:

sap.ui.getCore().getMessageManager().registerObject(this.getView(), true);

You can define custom types by inheriting from sap.ui.model.SimpleType and implementing the three methods formatValue, parseValue, and validateValue. -

Date
// The source value is given as string in "yyyy/MM/dd" format. The used output style is "long". The styles are language dependent.
// The following styles are possible: short, medium (default), long, full
// This might be the common use case.
oType = new TypeDate({source: {pattern: "yyyy/MM/dd"}, style: "long"}); 

// The source value is given as timestamp (long in ms). The used output pattern is "dd.MM.yyyy": e.g. 22.12.2010
oType = new TypeDate({source: {pattern: "timestamp"}, pattern: "dd.MM.yyyy"});
DateTime

When talking about exact points in time, time zones are imported. The formatted output of the DateTime type currently shows the "local" time which equals the time settings of the machine on which the browser runs. If the source value is given as a JavaScript Date object or as a timestamp, the exact moment is sufficiently defined. For string source values this value is interpreted in "local" time if it does not explicitly have a time zone. Currently, all accepted time zone notations must be based on GMT/UTC.

// The source value is given as string. The used input pattern depends on the locale settings (default). The used output pattern is "hh-mm-ss '/' yy-MM-dd": e.g. 06-48-48 / 43-08-22
oType = new TypeDateTime({source: {}, pattern: "hh-mm-ss '/' yy-MM-dd"}); 

// The source value is given as string in "dd.MM.yyyy HH:mm:ss X" format (timezone is defined in ISO8601 format, e.g. "+02:00"). The used output pattern depends on the locale settings (default).
oType = new TypeDateTime({source: {pattern: "dd.MM.yyyy HH:mm:ss X"}});

// The source value is given as string in "dd.MM.yyyy HH:mm:ss Z" format (timezone is defined in RFC822 format, e.g. "+0200"). The used output pattern depends on the locale settings (default).
oType = new TypeDateTime({source: {pattern: "dd.MM.yyyy HH:mm:ss Z"}});

// The source value is given as string in "dd.MM.yyyy HH:mm:ss z" format (timezone is currently defined as e.g. "GMT+02:00", "UTC+02:00", "UT+02:00" or "Z" (shortcut for "UTC+00:00")).
// The used output pattern depends on the locale settings (default).
oType = new TypeDateTime({source: {pattern: "dd.MM.yyyy HH:mm:ss z"}});

DateTimeInterval

var text = new Text({
    text: {
        parts: [
            {
                path: "StartsAt",
                // requires OData type to convert the date string into JavaScript Date object
                type: "sap.ui.model.odata.type.DateTimeOffset"
            },
            {
                path: "EndsAt",
                // requires OData type to convert the date string into JavaScript Date object
                type: "sap.ui.model.odata.type.DateTimeOffset"
            }
        ],
        type: "sap.ui.model.type.DateInterval",
        formatOptions: {
            format: "yMMMdd"
        }
    }
});

Data Export

// "Export" required from module "sap/ui/core/util/Export"
var oExport = new Export({

    // "ExportTypeCSV" required from module "sap/ui/core/util/ExportTypeCSV"
    // Type that will be used to generate the content. Own ExportType's can be created to support other formats
    exportType: new ExportTypeCSV({
        separatorChar: ";"
    }),

    // Pass in the model created above
    models: oModel,

    // binding information for the rows aggregation 
    rows: {
        path: "/" 
    },

    // column definitions with column name and binding info for the content
    columns: [
        {
            name: "First name",
            template: {
                content: {
                    path: "firstname"
                }
            }
        },
        {
            name: "Last name",
            template: {
                content: {
                    path: "lastname"
                }
            }
        }
    ]
});

The export class provides a generate method that triggers the generation process and returns a jQuery Promise object.

oExport.generate().done(function(sContent) {
    console.log(sContent);
}).always(function() {
    this.destroy();
});

You can directly save the file by triggering a download. This calls the generate method internally and uses the file util class (sap/ui/core/util/File) to trigger the download.

oExport.saveFile().always(function() {
    this.destroy();
});

sap/ui/table/Table

The exportData method creates an export instance and fills the rows and columns with the table's rows/column definition, if not defined otherwise. This also includes filters and sorters that have been applied to the columns.

oTable.exportData({
    exportType: ExportTypeCSV()
})
.saveFile()
.always(function() {
    this.destroy();
});
rogerxu commented 4 years ago

Fragments

Reusing UI Parts: Fragments

Instantiation of Fragments

Programmatically Instantiating XML Fragments

sap.ui.require(["sap/ui/core/Fragment"], function(Fragment) {
    Fragment.load({
        name: "my.useful.VerySimpleUiPart",
        controller: oDummyController
    }).then(function(myButton) {
        // ...
    });

    var theSameButton = this.byId("btnInFragment"); // returns the button in the fragment
});

Instantiating Fragments in Declarative Views

In XML views, fragments are used like regular controls, or more precisely, like views.

<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns:core="sap.ui.core" controllerName="testdata.fragments.XMLViewController" >
    <core:Fragment               fragmentName="my.useful.SimpleUiPart" type="XML" />
    <core:Fragment id="xmlInXml" fragmentName="my.useful.SimpleUiPart" type="XML" />
</mvc:View>

Controller

The oController object given when instantiating a fragment does not need to be an object of type sap.ui.core.mvc.Controller.

var oDummyController = { 
    doSomething: function() {
        // do whatever should happen when the button in the fragment is pushed...
    } 
};

Unique IDs

The following rules apply for given IDs:

Dialogs as Fragments

The special aggregation dependents of sap.ui.core.Element can be used to connect the dialog to the lifecycle management and data binding of the view:

var view = this.getView();

var dialogController = {
    onCancelDialog: function() {
        var dialog = view.byId("dialog");
        dialog.close();
    }
};
var promise = Fragment.load({
    id: view.getId(),
    name: "testdata.fragments.XMLFragmentDialog",
    controller: this
}).then(function(oDialog) {
    this.getView().addDependent(oDialog);

    return oDialog;
}.bind(this));

promise.then(function(dialog) {
    dialog.bindElement(contextPath, modelName);

    dialog.open();
}.bind(this));

Fragments with Multiple Root Nodes

<core:FragmentDefinition xmlns="sap.m" xmlns:core="sap.ui.core">
    <Label text="These controls are within one multi-root Fragment:" />
    <Input />
    <Button text="Still in the same Fragment" />
</core:FragmentDefinition>
rogerxu commented 4 years ago

Working with Controls

Working with Controls

Using Predefined CSS Margin Classes

Unlike paddings, margins are transparent, are not part of the control's clickable area, and they collapse with adjacent margins, meaning that they do not add to each other. For instance, if you have two 32px margins next to each other, the result is that only one 32px margin is displayed, not 64px of space.

Controls with 100% Width

When applying classes with horizontal margins to a control, such as sapUiSmallMargin or sapUiSmallMarginBegin, for example, make sure that your control doesn’t have a 100% width. If your control has a width property (which most controls have), set the width value to auto, for example:

<Panel width="auto" class="sapUiLargeMarginBegin sapUiLargeMarginBottom">

If your control does not have a width property but still has a default width of 100%, you can add our CSS class sapUiForceWidthAuto to your control, which ensures that the control’s default width is overwritten with the value auto. An example for such a control is sap.m.IconTabBar

Removing Margins

Using Container Content Padding CSS Classes

Field Groups

Field groups are built by means of a common fieldGroupIds array for a group that can be set for each control. When the user changes the focus to a control with a different fieldGroupIds array, the validateFieldGroup event is raised and bubbled in the control hierarchy.

<input fieldGroupIds="MyGroup,MyGroup2" /> 
var myVerticalLayout = new VerticalLayout({
    content: [myInput1, myInput2],
    validateFieldGroup: function(oEvent) {
        var aFieldGroup = oEvent.getParameter("fieldGroupIds");
        if (aFieldGroup.indexOf("MyGroup") > -1) {
            // do validation
            oEvent.bCancelBubble = true; // stop bubbling to the parent control
        }
    }
});
var aAllControlsWithFieldGroupId = myVerticalLayout.getControlsByFieldGroupId();              //all where fieldGroupId is not empty 
var aMyGroupControls             = myVerticalLayout.getControlsByFieldGroupId("myGroup");     //exact matches to myGroup
rogerxu commented 4 years ago

Error, Warning, and Info Messages

Error, Warning, and Info Messages

The central MessageManager for storing messages is available globally by calling sap.ui.getCore().getMessageManager() and the central MessageModel for managing messages is available by calling sap.ui.getCore().getMessageManager().getMessageModel().

Message Object

sap.ui.core.message.Message

Validation Messages

Validation messages refer to a control. They are created by the SAPUI5 framework when data is parsed, formatted, and validated according to defined data types. Such messages are propagated to one specific control.

Target

label0/text

Lifecycle

The messages are kept until a validation message for the property is created and assigned. If new data for the same property is received from the server, the validation messages are erased unless their persistent property is set to true.

Automatically Created Messages

Descriptor for Applications

You can activate the automatic message generation in the "sap.ui5" section of the manifest.json file as follows:

"sap.ui5": {
    "handleValidation": true
}

OData V2 Messages

OData V2 messages are either created automatically by sap.ui.model.odata.ODataMessageParser and processed by the sap.ui.model.odata.v2.ODataModel or can be created manually by the application.

Target

If a target is set, it must correspond to a binding path which is then used to propagate the message to the corresponding bindings. If these bindings belong to a control that implements the refreshDataState function, the control is able to react to data state changes.

Lifecycle

OData V2 messages are kept until a message from the server for the same path arrives. The server always sends all messages for a specific target which means that all current messages are replaced with the ones sent by the server, except for persistent UI messages. Back-end messages with property transition set to true are parsed to persistent UI messages.

Automatically Created Messages

The sap.ui.model.odata.v2.ODataModel supports automatic parsing of OData V2 messages by means of sap.ui.model.odata.ODataMessageParser.

OData V2 Message Parser

The ODataMessageParser is created automatically for all v2.ODataModel instances and parses all responses from the server. The ODataModel implements the message processor interface and is used to propagate the messages to the message manager. In case of an error response, the response body is parsed for error messages. In case of a successful response, the "sap-message" header is parsed as a JSON-formatted error object. The name of the header field can be changed by calling the setHeaderField() method on the ODataMessageParser.

Message Parser

If you have your own service implementation, for example, a JSON-based back end that also sends messages, you can implement your own message parser by implementing the sap.ui.core.message.MessageParser interface. The interface is very simple: It has only the parse and the setProcessor method. The parse method takes at least one parameter, that is, the response object from the server. The method can take more model-specific arguments. The setProcessor method takes only one argument, the processor object that is used to propagate the messages, this is usually the model instance.

The main task of the message parser is to retrieve the messages from the back end response and then calculate the message delta that is handed over to the message processor by means of the two parameters oldMessages and newMessages of the messageChange event. The oldMessages parameter specifies the messages that are to be removed, and the newMessages parameter specifies the messages that are to be added.

this.getProcessor().fireMessageChange({
    oldMessages: aRemovedMessages,
    newMessages: aNewMessages
});
rogerxu commented 4 years ago

Routing and Navigation

Routing and Navigation

image

Whenever a hash is added to a URL, the router checks whether there is a route with a matching pattern. The first matching route is taken and the corresponding target view is called.

Routing Configuration

Routes

Targets

Whenever a target is displayed, the corresponding view or component is loaded and added to the aggregation configured with the controlAggregation option of the control.

Config

The config section contains the global router configuration and default values that apply for all routes and targets.

Methods and Events for Navigation

Methods

navTo
var oRouter = this.getOwnerComponent().getRouter();
oRouter.navTo("product", {
  id: "5",
  productId: "3"
}, false);
display

The method uses the target name or an array of target names as only parameter.

Events

image

matched event on Route
var oRouter = this.getOwnerComponent().getRouter();
oRouter.getRoute("product").attachMatched(function(oEvent) {
    var id = oEvent.getParameter("arguments").id;
    this._selectItemWithId(id);
}, this);
display event on Target

This event is fired on the target instance when this target is added and displayed on the UI. The event has the following parameters:

created event on View

This event is fired on the view/component cache in SAPUI5 routing which can be fetched by calling the getViews() method on a router instance every time a new view or component has been created by navigation. The event has the following parameters:

Initializing and Accessing a Routing Instance

Initializing

return UIComponent.extend("YourComponentClassName", {
    init: function () {
        // call the init function of the parent
        UIComponent.prototype.init.apply(this, arguments);

        // this component should automatically initialize the router
        this.getRouter().initialize();
    }
});

Accessing

var oRouter = sap.ui.core.UIComponent.getRouterFor(oControllerOrView);
var oRouter = component.getRouter();

Working with Multiple Targets

Multiple Targets for the Same Route

All target instances are created and loaded in the order that they appear in the target option of a route when the pattern is matched.

Parent Relationship

Whenever a target has a parent, an instance of the parent is always created before an instance of the target is created.

A parent relationship between targets tightly couples the two targets together. The parent target is always displayed before the child target is displayed. The child target can't be displayed without first displaying the parent target. This approach is mainly used when the view in the child target is added to an aggregation of the view in the parent target.

Using the title Property in Targets

{
    "targets": {
        "products": {
            "type": "View",
            "path": "shop.products",
            "title": "Products Overview"
        }
    }
}

A route can display multiple targets and you can use the titleTarget option in the Route configuration to specify which target the title should be taken from explicitly. By default, the Route takes the title of the first target that has the title property defined.

{
    "routes": [{
        "pattern": "product/{id}/parts",
        "name": "ProductParts",
        "target": ["product", "productParts"],
        "titleTarget": "productParts"
    }],
    "targets": {
        "product": {
            "viewPath": "shop.products",
            "viewName": "Product",
            "title": "Product"
        },
        "productParts": {
            "viewPath": "shop.products",
            "viewName": "Product",
            "title": "Product Parts"
        }
    }
}
rogerxu commented 4 years ago

Adapting to Operating Systems And Devices

Adapting to Operating Systems And Devices

rogerxu commented 4 years ago

Integration Testing with One Page Acceptance Tests (OPA5)

Documentation - Demo Kit - SAPUI5 SDK

Limitations of OPA5

OPA Test

If you use opaQunit, OPA gives you the following three objects in your QUnit:

sap.ui.require([
    "sap/ui/test/Opa5",
    "sap/ui/test/opaQunit",
], function (Opa5, opaQUnit) {

  opaQunit("Should press a Button", function (Given, When, Then) {
        // Arrangements
        Given.iStartMyApp();

        //Actions
        When.iPressOnTheButton();

        // Assertions
        Then.theButtonShouldHaveADifferentText();
    });
});

Given: Defining Arrangements

var arrangements = new Opa5({
    iStartMyApp: function () {
        return this.iStartMyAppInAFrame("../index.html");
    }
});

When: Defining Actions

var actions = new Opa5({
    iPressOnTheButton: function () {
        return this.waitFor({
            viewName : "Main",
            id : "pressMeButton",
            actions : new Press(),
            errorMessage : "did not find the Button"
        });
    }
});

Then: Defining Assertions

var assertions = new Opa5({
    theButtonShouldHaveADifferentText: function () {
        return this.waitFor({
            viewName : "Main",
            id : "pressMeButton",
            matchers : new PropertyStrictEquals({
                name : "text",
                value : "I got pressed"
            }),
            success : function (oButton) {
                Opa5.assert.ok(true, "The button's text changed to: " + oButton.getText());
            },
            errorMessage : "did not change the Button's text"
        });
    }
});

Running the Test

Opa5.extendConfig({
    arrangements : arrangements,
    actions : actions,
    assertions : assertions,
    viewNamespace : "my.app.view.",
});

OPA Startup

Starting a UIComponent

You can use a UIComponent to run your OPA5 tests. To do this, you have to call the iStartMyUIComponent function on the OPA5 instance with an object that contains at least the name of your UIComponent.

var arrangements = new Opa5({
    iStartMyApp: function () {

        // configure mock server with the current options
        var oMockserverInitialized = mockserver.init({
            componentName: componentName
        });
        this.iWaitForPromise(oMockserverInitialized);

        // start the app UI component
        return this.iStartMyUIComponent({
            componentConfig: {
            name: componentName,
            async: true,
            },
            hash: oOptions.hash,
            autoWait: oOptions.autoWait,
        });
    },
});

Please note that OPA5 tests can only run for a single UIComponent. You first have to tear down the current UIComponent before starting an OPA5 test for another UIComponent, for example:

new Opa5().iTeardownMyApp();
// or
new Opa5().iTeardownMyUIComponent();

Starting an App in an iFrame

You can start only one iFrame at a time. An error will be thrown if you try to start an iFrame when one is already launched or if you try to teardown the iFrame before it is started. If an iFrame element is already present on the page, it will be used. The iFrame and test window must be in the same domain.

new Opa5().iStartMyAppInAFrame("index.html?responderOn=true");

The OPA iFrame launcher overwrites the iFrame's history API so we can later change the iFrame's hash, and pass parameters to the app.

You can remove the iFrame using one of the following methods:

new Opa5().iTeardownMyApp();
// or
new Opa5().iTeardownMyAppFrame();

Starting the app can be a slow operation so it is not recommended to do this for every test. However, it is good practice to group tests in modules and restart the app in every module to enable faster debugging of larger suites.

Cookbook

Application Parameters

Opa5.extendConfig({
    appParams: {
        "key": "value"
    }
});

URL Parameters

On startup, OPA parses window.location.href and extracts all search parameters starting with 'opa'. The prefix is removed and the resulting string has its first character changed to lower case. For example, the ?opaExecutionDelay=600 string in a URL sets the value of executionDelay to 600 ms. All OPA config parameters of primitive string and number types that are documented in Opa.resetConfig() could be overwritten.

Working with Message Toasts

To retrieve a message toast control and manipulate it accordingly, use standard jQuery selectors with the help of the check parameter of OPA5 waitFor method, as messageToast elements cannot be retrieved by interaction with the SAPUI5 API.

iShouldSeeMessageToastAppearance: function () {
    return this.waitFor({
        // Turn off autoWait
        autoWait: false,
        check: function () {
            // Locate the message toast using its class name in a jQuery function
            return Opa5.getJQuery()(".sapMMessageToast").length > 0;
        },
        success: function () {
            Opa5.assert.ok(true, "The message toast was shown");
        },
        errorMessage: "The message toast did not show up"
    });
}

Working with Busy Controls

There are OPA5 rules that limit the ways you can use busy controls. Some OPA5 features prevent you from locating controls while they are busy.

Deactivating Tests in Need of Adaptation

opaTodo and opaSkip are readily available to your test as globals.

If a test has to be adapted after recent changes, you have to disable it temporarily in order to have a successful build. A test which is commented out can easily be forgotten and its coverage value lost. opaTodo prompts you to uncomment the test once an adaptation is provided.

// The following test should report 1 assertion failure because there is no element with ID "saveButton"
// The test itself will be successful in QUnit2
// It will fail in QUnit1, as QUnit.todo is not yet available, and the result will be the same as for opaTest
opaTodo("Should press another button", function (Given, When, Then) {
    Given.iStartMyAppInAFrame("applicationUnderTest/index.html");

    When.waitFor({
        id : "saveButton",
        actions : new Press(),
        errorMessage : "Did not find the save button"
    });

    Then.iTeardownMyAppFrame();
});

// The following test will be skipped
// If opaTest is used instead, the test will fail
opaSkip("Should press another button", function (Given, When, Then) {
    Given.iStartMyAppInAFrame("applicationUnderTest/index.html");

    When.waitFor({
        id : "skipButton",
        actions : new Press(),
        errorMessage : "Did not find the skip button"
    });

    Then.iTeardownMyAppFrame();
});

Page Objects

Opa5.createPageObjects({

    //give a meaningful name for the test code
    onThe<Page Object> : {
        //Optional: a class extending Opa5, with utility functionality
        baseClass : fnSomeClassExtendingOpa5,

        actions : {
            //place all arrangements and actions here
            <iDoSomething> : function(){
                //always return this or a waitFor to allow chaining
                return this.waitFor({
                    //see documentation for possibilities
                });
            }
        },
        assertions : {
            //place all assertions here
            <iCheckSomething> : function(){
                //always return this or a waitFor to allow chaining
                return this.waitFor({
                    //see documentation for possibilities
                });
            }
        }
    }
});

The method in your test finds all actions at the Given and When object, the assertions will be at the Then object. Everything is prefixed with the page object name.

When.onThe<Page Object>.<iDoSomething>();

Then.onThe<Page Object>.<iCheckSomething>();

Matchers

Declarative Syntax

As of version 1.72, OPA5 supports the declarative matcher syntax that allows you to declare built-in matchers in a literal object. The syntax is inspired by control locators in UIVeri5 and promotes reuse between the two testing tools. A matcher declaration is a JSON object.

Only built-in matchers are allowed.

this.waitFor({
    controlType : "sap.m.Text",
    propertyStrictEquals: {
        name : "text",
        value : "foo"
    }
});
this.waitFor({
    controlType : "sap.m.Text",
    matchers: {
        propertyStrictEquals: {
            name : "text",
            value : "foo"
        }
    }
});

Retrieving Controls

Searching for Controls Inside a Dialog

Use the option searchOpenDialogs to restrict control search to open dialogs only.

Searching for Controls Inside a Fragment

Limit control search to a fragment with the option fragmentId.

Whether a control belongs to a fragment is only relevant when the fragment has a user-assigned ID, which is different from the ID of its parent view.

Searching for Missing Controls

you can look for controls that are invisible, disabled, or noninteractable by using the respective waitFor boolean properties: visible, enabled, and interactable.

Searching for Disabled Controls

When enabled is set to true, only enabled controls will match. When enabled is set to false, both enabled and disabled controls will match.

Using the autoWait Parameter

Actions

Using OpaBuilder

rogerxu commented 4 years ago

Theming

Theming

rogerxu commented 4 years ago

Localization

Localization

Language Code

Identifying the Language Code / Locale

The SAPUI5 configuration options accept the following formats:

SAP Language Code BCP47 Language Tag Description
ZH zh-Hans ZH is the SAP language code for Simplified Chinese. The most generic representation in BCP47 is zh-Hanszh-CN (Chinese, China) is another representation, but SAPUI5 decided to use zh-Hans.
ZF zh-Hant ZF is the SAP language code for Traditional Chinese. The most generic representation in BCP47 is zh-Hantzh-TW (Chinese, Taiwan) is another representation, but SAPUI5 decided to use zh-Hant.
1Q en-US-x-saptrc 1Q is a technical SAP language code used in support scenarios, for example for translation issues. When you select this language code, the technical keys are displayed instead of the actual data. As no ISO639 code for this exists, the information has been added as a BCP47 private extension to the en-US language tag: "trc" stands for "trace" or "traceability".
2Q en-US-x-sappsd 2Q is also used as a technical SAP language code in support scenarios and displays a pseudo translation ("psd" in the private extensions name).

If you develop your app to run in the SAP Fiori launchpad, all other SAP-proprietary language codes are handled by the SAP Fiori launchpad.

Current Language Code

The sources are ordered increasingly by priority and the last available user language/locale wins:

  1. Hard-coded SAPUI5 default locale en
  2. Potentially configured browser language (window.navigator.browserLanguage); for Internet Explorer this is the language of the operating system
  3. Potentially configured user language (window.navigator.userLanguage); for Internet Explorer this is the language in the region settings
  4. General language information from the browser (window.navigator.language)
  5. Android: Language contained in the user agent string (window.navigator.userAgent)
  6. First language from the list of the user’s preferred languages (window.navigator.languages[0]) (For more information, see https://developer.mozilla.org)
  7. Locale configured in the application coding (For more information, see API Reference: sap.ui.core.Configuration. )
  8. Locale configured via URL parameters

You use the configuration API to retrieve the resulting current language as follows:

var sCurrentLocale = sap.ui.getCore().getConfiguration().getLanguage();

Resource Bundles

A resource bundle file is a Java properties file (as described in the Javadoc of class java.util.Properties ). It contains key-value pairs where the values are the language-dependent texts and the keys are language-independent and used by the application to identify and access the corresponding values.

Resource bundles are a collection of *.properties files. All files are named with the same base name (prefix identifying the resource bundle), an optional suffix that identifies the language contained in each file, and the fixed .properties extension.

When a localized text is needed, the application uses the SAPUI5 APIs to load the properties file that matches the current language best. To retrieve a text from the properties file, the application uses the (language-independent) key. If no text can be found for this key, the next best matching file is loaded and checked for the text. Finally, if no file matches, the raw file is loaded and checked.

Use of Localized Texts in Applications

Using sap/base/i18n/ResourceBundle

// "ResourceBundle" required from module "sap/base/i18n/ResourceBundle"
ResourceBundle.create({
    url : sUrl, 
    locale: sLocale,
    async: true
}).then(function(oBundle) {
    var sText = oBundle.getText(sKey, ["param1", "param2"]);
});

Data Binding

The ResourceModel is a wrapper for resource bundles that exposes the localized texts as a model for data binding. You can instantiate the ResourceModel either with bundleName (name of a resource bundle that equals a SAPUI5 module name within the define/require concept), or a bundleUrl, which points to a resource bundle.

var oModel = new ResourceModel({
  bundleName: "myBundle",
  bundleLocale: "en",
  async: true
});
var oControl = new Button({
  id: "myButton",
  text: "{i18n>MY_BUTTON_TEXT}"
});
// attach the resource model with the symbolic name "i18n"
// The texts are resolved via databinding, once the resource bundle file was loaded
oControl.setModel(oModel, "i18n");

The current data binding implementation does not allow to pass parameters to your texts in the resource bundle.

rogerxu commented 4 years ago

Accessibility

Accessibility

rogerxu commented 4 years ago

Drag and Drop

Drag and Drop

<Table
    id="table"
    width="auto"
    items="{
        path: 'store>items',
        templateSharable: false,
        key: 'id'
    }">
    <headerToolbar>
        <OverflowToolbar>
            <Title
                id="tableHeader"
                text="{view>/worklistTableTitle}"
                level="H3" />
            <ToolbarSpacer />
        </OverflowToolbar>
    </headerToolbar>
    <columns>
    </columns>
    <dragDropConfig>
        <dnd:DragDropInfo
            sourceAggregation="items"
            targetAggregation="items"
            dropPosition="Between"
            drop="onDropTable" />
        </dragDropConfig>
    <items>
    </items>
</Table>
onDropTable: function (oEvent) {
  var oDraggedItem = oEvent.getParameter("draggedControl");
  var oDroppedItem = oEvent.getParameter("droppedControl");
  var sDropPosition = oEvent.getParameter("dropPosition");

  var table = oEvent.getSource().getParent();
  var iOldItemIndex = table.indexOfItem(oDraggedItem);
  var iDroppedItemIndex = table.indexOfItem(oDroppedItem);

  // find the new index of the dragged row depending on the drop position
  var iNewItemIndex = iDroppedItemIndex;
  if (iOldItemIndex < iDroppedItemIndex) {
      iNewItemIndex = iDroppedItemIndex + (sDropPosition === "After" ? 0 : -1);
  } else {
      iNewItemIndex = iDroppedItemIndex + (sDropPosition === "After" ? 1 : 0);
  }

  // move item
  var binding = table.getBinding("items");
  var items = binding.getContext().getProperty(binding.getPath());
  this._moveItemInArray(items, iOldItemIndex, iNewItemIndex);

  // refresh the binding
  binding.refresh();
},
_moveItemInArray: function (array, oldIndex, newIndex) {
  var item = array[oldIndex];

  // remove item at old index
  array.splice(oldIndex, 1);

  // insert item at new index
  array.splice(newIndex, 0, item);
},
rogerxu commented 4 years ago

Spreadsheet Export

Spreadsheet Export

rogerxu commented 4 years ago

Troubleshooting

Troubleshooting

Debugging

Logging and Tracking

Technical Information Dialog

Diagnostics

Support Assistant

rogerxu commented 4 years ago

Layout

FormLayout

ResponsiveLayout

https://sapui5.hana.ondemand.com/#/api/sap.ui.layout.form.ResponsiveLayout

The ResponsiveLayout renders a Form with a responsive layout. Internally the ResponsiveFlowLayout is used. The responsiveness of this layout tries to best use the available space. This means that the order of the FormContainers, labels and fields depends on the available space.

On the FormContainers, FormElements, labels and content fields, ResponsiveFlowLayoutData can be used to change the default rendering.

We suggest using the ResponsiveGridLayout instead of this layout because this is easier to consume and brings more stable responsive output.

Note: If ResponsiveFlowLayoutData are used this may result in a much more complex layout than the default one. This means that in some cases, the calculation for the other content may not bring the expected result. In such cases, ResponsiveFlowLayoutData should be used for all content controls to disable the default behavior.

This control cannot be used stand-alone, it just renders a Form, so it must be assigned to a Form using the layout aggregation.

ResponsiveGridLayout

https://sapui5.hana.ondemand.com/#/api/sap.ui.layout.form.ResponsiveGridLayout

The ResponsiveGridLayout control renders a Form using a responsive grid. Internally the Grid control is used for rendering. Using this layout, the Form is rendered in a responsive way. Depending on the available space, the FormContainers are rendered in one or different columns and the labels are rendered in the same row as the fields or above the fields. This behavior can be influenced by the properties of this layout control.

On the FormContainers, labels and content fields, GridData can be used to change the default rendering. GridData is not supported for FormElements.

Note: If GridData is used, this may result in a much more complex layout than the default one. This means that in some cases, the calculation for the other content may not bring the expected result. In such cases, GridData should be used for all content controls to disable the default behavior.

This control cannot be used stand-alone, it just renders a Form, so it must be assigned to a Form using the layout aggregation.

GridLayout

https://sapui5.hana.ondemand.com/#/api/sap.ui.layout.form.GridLayout

Deprecated as of version 1.67.0. as sap.ui.commons library is deprecated and the GridLayout must not be used in responsive applications. Please use ResponsiveGridLayout or ColumnLayout instead.

ColumnLayout

https://sapui5.hana.ondemand.com/#/api/sap.ui.layout.form.ColumnLayout

The ColumnLayout control renders a Form control in a column-based responsive way. Depending on its size, the Form control is divided into one or more columns. (XL - max. 4 columns, L - max. 3 columns, M - max. 2 columns and S - 1 column.)

The FormContainer elements are spread out to the columns depending on the number of FormContainer elements and their size. For example, if there are 4 columns and 2 FormContainer elements, each FormContainer element will use 2 columns. If there are 3 columns and 2 FormContainer elements, the larger one will use 2 columns, the smaller one 1 column. The size of a FormContainer element will be determined based on the number of visible FormElement elements assigned to it. If there are more FormContainer elements than columns, every FormContainer element uses only one column. So the last row of the Form control will not be fully used.

The default size of the FormContainer element can be overwritten by using ColumnContainerData as LayoutData. If one FormContainer element has ColumnContainerData set, the size calculation of the other FormContainer elements might not lead to the expected result. In this case, use ColumnContainerData also for the other FormContainer elements.

The FormElement elements are spread out to the columns of a FormContainer element arranged in a newspaper-like order. The position of the labels and fields depends on the size of the used column. If there is enough space, the labels are beside the fields, otherwise above the fields.

The default size of a content control of a FormElement element can be overwritten using ColumnElementData as LayoutData. If one control assigned to a FormElement element has ColumnElementData set, the size calculation of the other controls assigned to the FormElement element might not lead to the expected result. In this case, use ColumnElementData for the other controls, assigned to the FormElement element, too.

The placement of the FormElement elements is made by the browser column-count logic. So this can be different in different browsers and lead in some cases to other results than might be expected.