Open rogerxu opened 7 years ago
SAPUI5 deploy custom control library to ABAP repository using Grunt | SAP Blogs
openui5/controllibraries.md at master · SAP/openui5
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";
});
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>
Bootstrapping: Loading and Initializing
<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 of the SAPUI5 Runtime
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 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 |
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;
});
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");
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>
<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>
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();
});
App Overview: The Basic Files of Your App
App Initialization: What Happens When an App Is Started?
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
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();
});
}
});
});
sap.ui.require
does not support relative path.
sap.ui.require([
"jquery.sap.global",
"com/my/app/util/VersionUtil"
], function(jQuery, VersionUtil) {
"use strict";
});
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');
}
sap.ui.loader.config({
paths: {
"my/module": "https://example.com/resources/my/module"
}
});
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.
<mvc:View
xmlns:mvc="sap.ui.core.mvc"
xmlns:layout="sap.ui.layout"
xmlns:form="sap.ui.layout.form"
xmlns="sap.m">
</mvc:View>
core:require
statement can be used to access static functions of the required modules.<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)"/>
<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>
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");
onInit()
: Called when a view is instantiated and its controls (if available) have already been created; used to modify the view before it is displayed to bind event handlers and do other one-time initializationonExit()
: Called when the view is destroyed; used to free resources and finalize activitiesonBeforeRendering()
: Called every time the view is rendered, before the renderer is called and the HTML is placed in the DOM tree.onAfterRendering()
: Called when the view has been rendered, and therefore, its HTML is part of the document; used to do post-rendering manipulations of the HTML. SAPUI5 controls get this hook after being rendered.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.
All public methods need to stay compatible:
@since
version to tell the consumer on which version this method was introduced.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() {}
}
});
});
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--
:
myContainerView--ButtonView1
myContainerView--ButtonView2
The button view has the following IDs:
ButtonView1--aButton
ButtonView2--aButton
To get the button control, use the following code:
var oButton = oButtonView1.byId("aButton");
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>
<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");
<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>
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]);
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
.
false
the lifecycle is controlled by the framework. It will destroy the template when the binding is removed (unbindAggregation
, unbindItems
)true
the template is not destroyed when (the binding of) the aggregated object is destroyed. Use this option in the following cases only:
templateShareable
is set to true
, the template will not be cloned, when it is set to false
it will be cloned when the parent is cloned.undefined
, (neither true
nor false
), the framework checks at several points in time whether all list bindings are removed. If there are no bindings, the templates is marked as candidate for destroy()
, but it is not immediately destroyed. The candidate is destroyed in the following cases:
To leave the parameter undefined
is very error-prone, therefore we don't recommend this! Always set the parameter explicitly to true
or false
.
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();
<TextField value="{
parts: [
{path:'birthday/day'},
{path:'birthday/month'},
{path:'birthday/year'}
],
formatter:'my.globalFormatter'
}"/>
{=expression}
This variant uses one-way binding. This allows the automatic recalculation if the model values change.{:=expression}
This variant uses one-time binding, meaning that the value is calculated only once. This variant needs less resources because no change listeners to the model have to be maintained.You can embed values from the model layer into an expression as additional bindings by using one of the following syntaxes:
${binding}
%{binding}
- It is just a shortcut for ${path: 'binding', targetType: 'any'}
<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 < 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>
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)");
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:
formatOptions
: Format options define how a value is formatted and displayed in the UI.constraints
: Constraints are optional and define how an input value entered in the UI should look like. During parsing the value is validated against these constraints.<Input value="{
path: '/number',
type: 'sap.ui.model.type.Integer',
formatOptions: {
minIntegerDigits: 3
},
constraints: {
maximum: 1000
}
}" />
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
. -
formatValue
is called whenever the value in the model is changed to convert it to the type of the control property it is bound to, and may throw a FormatException
.parseValue
is called whenever the user has modified a value in the UI and the change is transported back into the model. It may throw a ParseException
if the value cannot be converted.validateValue
is called to check additional constraints, such as minimum or maximum value, and throws a ValidateException
if any constraints are violated.// 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"});
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"}});
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"
}
}
});
// "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();
});
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
});
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>
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...
}
};
The following rules apply for given IDs:
Given IDs are prefixed with only the view ID when no fragment ID was given
var myControl = this.byId("myControl");
Given IDs are prefixed with both view ID and fragment ID when a fragment ID was given
var myControl = this.byId(Fragment.createId("myFrag", "myControl"));
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));
<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>
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.
sapUiTinyMargin
sapUiSmallMargin
sapUiMediumMargin
sapUiLargeMargin
sapUiTinyNegativeMarginBeginEnd
sapUiResponsiveMargin
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
sapUiNoMargin
sapUiNoContentPadding
sapUiContentPadding
sapUiResponsiveContentPadding
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
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()
.
sap.ui.core.message.Message
sap.ui.core.message.ControlMessageProcessor
propagates these messages to the affected control.sap.ui.model.Model
propagates these messages to affected bindings.sap.ui.core.message.ControlMessageProcessor
.sap.ui.core.MessageType
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.
label0/text
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
.
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 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.
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.
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.
The sap.ui.model.odata.v2.ODataModel
supports automatic parsing of OData V2 messages by means of sap.ui.model.odata.ODataMessageParser
.
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
.
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
});
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.
product/settings
product/{id}
product/{id}/detail/:detailId:
product/:?query:
product/{id}/:detail*:
name
of the route (unique within one router instance)pattern
as hash part of the URL that matches the routetarget
as defined in the targets
sectiontitleTarget
to specify from which target the title is taken when multiple targets are displayed. If no titleTarget
is defined, the first target that has a title is chosen.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.
target
keytype
to specify whether the target is a view or a componentname
to specify the name of the view or componentconfig
section.
viewType
(e.g. XML) which is valid only when the type
is set to "View"id
of the view or component instance
A view or component instance is cached in SAPUI5 routing under the combination of its name
and id
.viewLevel
controlId
of the control that is used as the parent to insert the view or component (e.g. app
)controlAggregation
target aggregation of the control with controlId
to which the view or component is addedparent
: the key of another target which a view is created and added before the target view or component is addedpath
: the namespace of the view or componenttargetParent
where the control with the controlId
is located (see Working with Multiple Targets); this option is set automatically for the root view of a component if the router instance is instantiated by the component.clearAggregation
specifies whether the aggregation should be cleared before adding the new view instance.
When you use the sap.m.routing.Router
the default is false
, for sap.ui.core.routing.Router
it is true
.transition
defines how the transition happens; you can choose between slide
(default), flip
, fade
, and show
.title
contains either a static text or a valid binding syntax, e.g. to an i18n model, which is resolved under the binding context of the viewThe config
section contains the global router configuration and default values that apply for all routes and targets.
routerClass
defines which router is used.
You can either use class sap.ui.core.routing.Router
(default) or sap.m.routing.Router
. homeRoute
defines the route whose target title is inserted as the first entry in the title history in the titleChanged
event or in the return value of sap.ui.core.routing.Router.prototype.getTitleHistory
.async
defines whether targets are loaded asynchronously; the default value is false
. We recommend setting this parameter to true
to improve performance.bypassed
parameter, you specify the navigation target that is used whenever no navigation pattern is matched. If you use this setting, you also have to define a corresponding target in the targets section.navTo
name
of the routeroute
parametersreplace
(default: false
) to define whether the hash should be replaced (no new browser history entry) or set (browser history entry)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.
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:
object
for the instance which is displayed; this is either a View instance or a ComponentContainer instance which wraps the loaded componentcontrol
in which the target object is displayedconfig
of the targetdata
of the object passed when calling the display methodcreated
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:
object
for the created instanceoptions
containing additional optionsreturn 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();
}
});
var oRouter = sap.ui.core.UIComponent.getRouterFor(oControllerOrView);
var oRouter = component.getRouter();
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.
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.
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"
}
}
}
Documentation - Demo Kit - SAPUI5 SDK
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();
});
});
var arrangements = new Opa5({
iStartMyApp: function () {
return this.iStartMyAppInAFrame("../index.html");
}
});
var actions = new Opa5({
iPressOnTheButton: function () {
return this.waitFor({
viewName : "Main",
id : "pressMeButton",
actions : new Press(),
errorMessage : "did not find the Button"
});
}
});
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"
});
}
});
Opa5.extendConfig({
arrangements : arrangements,
actions : actions,
assertions : assertions,
viewNamespace : "my.app.view.",
});
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();
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.
Opa5.extendConfig({
appParams: {
"key": "value"
}
});
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.
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"
});
}
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.
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();
});
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>();
sap.ui.test.matchers.Properties
sap.ui.test.matchers.Ancestor
sap.ui.test.matchers.Descendant
sap.ui.test.matchers.BindingPath
sap.ui.test.matchers.LabelFor
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"
}
});
matchers
parameterthis.waitFor({
controlType : "sap.m.Text",
matchers: {
propertyStrictEquals: {
name : "text",
value : "foo"
}
}
});
Use the option searchOpenDialogs
to restrict control search to open dialogs only.
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.
you can look for controls that are invisible, disabled, or noninteractable by using the respective waitFor
boolean properties: visible
, enabled
, and interactable
.
When enabled
is set to true
, only enabled controls will match. When enabled
is set to false
, both enabled and disabled controls will match.
autoWait
ParameterOpaBuilder
Identifying the Language Code / Locale
The SAPUI5 configuration options accept the following formats:
sap-language
)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-Hans. zh-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-Hant. zh-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.
The sources are ordered increasingly by priority and the last available user language/locale wins:
en
window.navigator.browserLanguage
); for Internet Explorer this is the language of the operating systemwindow.navigator.userLanguage
); for Internet Explorer this is the language in the region settingswindow.navigator.language
)window.navigator.userAgent
)window.navigator.languages[0]
) (For more information, see https://developer.mozilla.org)sap.ui.core.Configuration
. )You use the configuration API to retrieve the resulting current language as follows:
var sCurrentLocale = sap.ui.getCore().getConfiguration().getLanguage();
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.
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"]);
});
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.
<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);
},
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.
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.
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.
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.
SAPUI5 SDK - Demo Kit v2.0
Essentials