@sapui5/sap.ui.export 1.130.0 → 1.131.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sapui5/sap.ui.export",
3
- "version": "1.130.0",
3
+ "version": "1.131.0",
4
4
  "description": "SAPUI5 Library sap.ui.export",
5
5
  "homepage": "https://sap.github.io/ui5-tooling/pages/SAPUI5/",
6
6
  "author": "SAP SE (https://www.sap.com)",
@@ -5,7 +5,7 @@
5
5
  <vendor>SAP SE</vendor>
6
6
  <copyright>SAPUI5
7
7
  * (c) Copyright 2009-2024 SAP SE. All rights reserved.</copyright>
8
- <version>1.130.0</version>
8
+ <version>1.131.0</version>
9
9
 
10
10
  <documentation>UI5 library: sap.ui.export</documentation>
11
11
 
@@ -4,12 +4,12 @@
4
4
  */
5
5
 
6
6
  sap.ui.define([
7
- 'sap/ui/core/Core',
8
- 'sap/ui/base/EventProvider',
9
- 'sap/base/Log',
10
- 'sap/ui/export/ExportUtils'
7
+ "sap/ui/core/Core",
8
+ "sap/ui/base/EventProvider",
9
+ "sap/base/Log",
10
+ "sap/ui/export/ExportUtils"
11
11
  ], function(Core, EventProvider, Log, ExportUtils) {
12
- 'use strict';
12
+ "use strict";
13
13
 
14
14
  /**
15
15
  * Base class for specific SAPUI5 export implementations. This class contains abstract functions that need to be implemented.
@@ -27,14 +27,14 @@ sap.ui.define([
27
27
  * @class The <code>sap.ui.export.ExportBase</code> class allows you to export table data from a UI5 application to certain formats. This class is an abstract class that requires specific implementations for each file format.
28
28
  *
29
29
  * @author SAP SE
30
- * @version 1.130.0
30
+ * @version 1.131.0
31
31
  *
32
32
  * @since 1.96
33
33
  * @alias sap.ui.export.ExportBase
34
34
  * @extends sap.ui.base.EventProvider
35
35
  * @public
36
36
  */
37
- var ExportBase = EventProvider.extend('sap.ui.export.ExportBase', {
37
+ const ExportBase = EventProvider.extend("sap.ui.export.ExportBase", {
38
38
 
39
39
  constructor: function(mSettings, mCapabilities) {
40
40
  EventProvider.call(this, mSettings);
@@ -43,14 +43,14 @@ sap.ui.define([
43
43
 
44
44
  /* Default settings */
45
45
  this._mSettings = {
46
- fileName: 'Export'
46
+ fileName: "Export"
47
47
  };
48
48
 
49
49
  /* Only apply supported properties */
50
50
  // IMPORTANT: keep count before dataSource to ensure that the expected count can be used for dataSource string
51
- ['count', 'dataSource', 'fileName', 'fileType', 'workbook'].forEach(function(sProperty) {
52
- if (typeof mSettings[sProperty] !== 'undefined') {
53
- this._mSettings[sProperty] = sProperty !== 'dataSource' ? mSettings[sProperty] : this.processDataSource(mSettings[sProperty]);
51
+ ["count", "dataSource", "fileName", "fileType", "workbook"].forEach(function(sProperty) {
52
+ if (typeof mSettings[sProperty] !== "undefined") {
53
+ this._mSettings[sProperty] = sProperty !== "dataSource" ? mSettings[sProperty] : this.processDataSource(mSettings[sProperty]);
54
54
  }
55
55
  }.bind(this));
56
56
 
@@ -94,7 +94,7 @@ sap.ui.define([
94
94
  * @public
95
95
  */
96
96
  ExportBase.prototype.attachBeforeExport = function(oData, fnHandler, oListener) {
97
- return this.attachEvent('beforeExport', oData, fnHandler, oListener);
97
+ return this.attachEvent("beforeExport", oData, fnHandler, oListener);
98
98
  };
99
99
 
100
100
  /**
@@ -111,7 +111,7 @@ sap.ui.define([
111
111
  * @public
112
112
  */
113
113
  ExportBase.prototype.detachBeforeExport = function(fnHandler, oListener) {
114
- return this.detachEvent('beforeExport', fnHandler, oListener);
114
+ return this.detachEvent("beforeExport", fnHandler, oListener);
115
115
  };
116
116
 
117
117
  /**
@@ -138,7 +138,7 @@ sap.ui.define([
138
138
  * @public
139
139
  */
140
140
  ExportBase.prototype.cancel = function() {
141
- throw new Error('Abstract function not implemented');
141
+ throw new Error("Abstract function not implemented");
142
142
  };
143
143
 
144
144
  /**
@@ -153,7 +153,7 @@ sap.ui.define([
153
153
  * @public
154
154
  */
155
155
  ExportBase.prototype.processDataSource = function(oDataSource) {
156
- throw new Error('Abstract function not implemented');
156
+ throw new Error("Abstract function not implemented");
157
157
  };
158
158
 
159
159
  /**
@@ -165,7 +165,7 @@ sap.ui.define([
165
165
  * @private
166
166
  */
167
167
  ExportBase.prototype.setDefaultExportSettings = function(mSettings) {
168
- throw new Error('Abstract function not implemented');
168
+ throw new Error("Abstract function not implemented");
169
169
  };
170
170
 
171
171
  /**
@@ -177,7 +177,7 @@ sap.ui.define([
177
177
  * @private
178
178
  */
179
179
  ExportBase.prototype.createBuildPromise = function(mSettings) {
180
- throw new Error('Abstract function not implemented');
180
+ throw new Error("Abstract function not implemented");
181
181
  };
182
182
 
183
183
  /**
@@ -188,7 +188,7 @@ sap.ui.define([
188
188
  * @public
189
189
  */
190
190
  ExportBase.prototype.getMimeType = function() {
191
- throw new Error('Abstract function not implemented');
191
+ throw new Error("Abstract function not implemented");
192
192
  };
193
193
 
194
194
  /**
@@ -200,17 +200,17 @@ sap.ui.define([
200
200
  * @async
201
201
  */
202
202
  ExportBase.prototype.build = async function() {
203
- var mParameters = this._mSettings;
203
+ const mParameters = this._mSettings;
204
204
 
205
205
  if (this.bIsDestroyed) {
206
- var sMessage = this.getMetadata().getName() + ': Cannot trigger build - the object has been destroyed';
206
+ const sMessage = this.getMetadata().getName() + ": Cannot trigger build - the object has been destroyed";
207
207
 
208
208
  Log.error(sMessage);
209
209
  return Promise.reject(sMessage);
210
210
  }
211
211
 
212
212
  await this.setDefaultExportSettings(mParameters);
213
- const bExecuteDefaultAction = this.fireEvent('beforeExport', {exportSettings: mParameters}, true, false);
213
+ const bExecuteDefaultAction = this.fireEvent("beforeExport", {exportSettings: mParameters}, true, false);
214
214
 
215
215
  if (!bExecuteDefaultAction) {
216
216
  return Promise.resolve();
@@ -27,7 +27,7 @@ sap.ui.define([
27
27
  * @class The <code>sap.ui.export.ExportHandler</code> class allows you to export table data from an SAPUI5 application.
28
28
  *
29
29
  * @author SAP SE
30
- * @version 1.130.0
30
+ * @version 1.131.0
31
31
  *
32
32
  * @since 1.102
33
33
  * @alias sap.ui.export.ExportHandler
@@ -144,7 +144,7 @@ sap.ui.define([
144
144
  * @class Utilities related to export to enable reuse in integration scenarios (e.g. tables).
145
145
  *
146
146
  * @author SAP SE
147
- * @version 1.130.0
147
+ * @version 1.131.0
148
148
  *
149
149
  * @since 1.59
150
150
  * @alias sap.ui.export.ExportUtils
@@ -1218,7 +1218,7 @@ sap.ui.define([
1218
1218
  *
1219
1219
  * @param {object} oContext Context object
1220
1220
  * @param {string} [oContext.application] Name of the application (default: "SAP UI5")
1221
- * @param {string} [oContext.version] Application version (default: "1.130.0")
1221
+ * @param {string} [oContext.version] Application version (default: "1.131.0")
1222
1222
  * @param {string} [oContext.title] Title that will be written to the file (NOT the filename)
1223
1223
  * @param {string} [oContext.modifiedBy] Optional user context that will be written to the file
1224
1224
  * @param {string} [oContext.sheetName] Name of the data sheet - Maximum length of 31 characters
@@ -26,7 +26,7 @@ sap.ui.define([
26
26
  * @class The <code>sap.ui.export.PortableDocument</code> class allows you to export table data from a UI5 application to a Portable Document Format (*.PDF) file.
27
27
  *
28
28
  * @author SAP SE
29
- * @version 1.130.0
29
+ * @version 1.131.0
30
30
  *
31
31
  * @since 1.96
32
32
  * @alias sap.ui.export.PortableDocument
@@ -90,7 +90,7 @@ sap.ui.define([
90
90
  * <li><code>workbook.context</code> - Context object that will be applied to the generated file. It may contain the following fields:</li>
91
91
  * <ul>
92
92
  * <li><code>application</code> (string) - The application that creates the XLSX document (default: "SAP UI5")</li>
93
- * <li><code>version</code> (string) - Application version that creates the XLSX document (default: "1.130.0")</li>
93
+ * <li><code>version</code> (string) - Application version that creates the XLSX document (default: "1.131.0")</li>
94
94
  * <li><code>title</code> (string) - Title of the XLSX document (NOT the filename)</li>
95
95
  * <li><code>modifiedBy</code> (string) - User context for the XLSX document</li>
96
96
  * <li><code>sheetName</code> (string) - The label of the data sheet</li>
@@ -134,7 +134,7 @@ sap.ui.define([
134
134
  *
135
135
  * Example:
136
136
  * <pre>
137
- * var oSpreadsheet = new sap.ui.export.Spreadsheet(mSettings);
137
+ * const oSpreadsheet = new sap.ui.export.Spreadsheet(mSettings);
138
138
  * oSpreadsheet.build();
139
139
  * </pre>
140
140
  *
@@ -145,7 +145,7 @@ sap.ui.define([
145
145
  *
146
146
  * Example:
147
147
  * <pre>
148
- * var oSpreadsheet = new sap.ui.export.Spreadsheet(mSettings);
148
+ * const oSpreadsheet = new sap.ui.export.Spreadsheet(mSettings);
149
149
  * oSpreadsheet.onprogress = function(iValue) {
150
150
  * {@link sap.base.Log#debug Log.debug}("Export: %" + iValue + " completed");
151
151
  * };
@@ -157,7 +157,7 @@ sap.ui.define([
157
157
  *
158
158
  * Example of column configuration:
159
159
  * <pre>
160
- * var aColumns = [];
160
+ * const aColumns = [];
161
161
  * aColumns.push({
162
162
  * label: "Name",
163
163
  * property: "name"
@@ -169,12 +169,12 @@ sap.ui.define([
169
169
  * scale: 2
170
170
  * });
171
171
  *
172
- * var mSettings = {
172
+ * const mSettings = {
173
173
  * workbook: {
174
174
  * columns: aColumns,
175
175
  * context: {
176
176
  * application: 'Debug Test Application',
177
- * version: '1.130.0',
177
+ * version: '1.131.0',
178
178
  * title: 'Some random title',
179
179
  * modifiedBy: 'John Doe',
180
180
  * metaSheetName: 'Custom metadata',
@@ -202,7 +202,7 @@ sap.ui.define([
202
202
  * dataSource: mDataSource,
203
203
  * fileName: "salary.xlsx"
204
204
  * };
205
- * var oSpreadsheet = new sap.ui.export.Spreadsheet(mSettings);
205
+ * const oSpreadsheet = new sap.ui.export.Spreadsheet(mSettings);
206
206
  * oSpreadsheet.build();
207
207
  * </pre>
208
208
 
@@ -286,7 +286,7 @@ sap.ui.define([
286
286
  * @class The <code>sap.ui.export.Spreadsheet</code> class allows you to export table data from a UI5 application to a spreadsheet file.
287
287
  *
288
288
  * @author SAP SE
289
- * @version 1.130.0
289
+ * @version 1.131.0
290
290
  *
291
291
  * @since 1.50
292
292
  * @alias sap.ui.export.Spreadsheet
@@ -294,7 +294,7 @@ sap.ui.define([
294
294
  * @see {@link topic:2691788a08fc43f7bf269ea7c6336caf Spreadsheet}
295
295
  * @public
296
296
  */
297
- var Spreadsheet = ExportBase.extend(CLASS_NAME, {
297
+ const Spreadsheet = ExportBase.extend(CLASS_NAME, {
298
298
 
299
299
  constructor: function(mSettings) {
300
300
  ExportBase.call(this, mSettings);
@@ -344,8 +344,6 @@ sap.ui.define([
344
344
  * @private
345
345
  */
346
346
  Spreadsheet.prototype.setDefaultExportSettings = async function(mParameters) {
347
- var mCurrencySettings, mUnitSettings, oWorkbookContext, sCurrencyCode;
348
-
349
347
  const oResourceBundle = await ExportUtils.getResourceBundle();
350
348
 
351
349
  /* Attach timezone customizing */
@@ -355,7 +353,7 @@ sap.ui.define([
355
353
  * Check if a document title and a sheet name have been defined in the 'context' settings.
356
354
  * Otherwise use default resource bundle properties
357
355
  */
358
- oWorkbookContext = mParameters.workbook.context;
356
+ let oWorkbookContext = mParameters.workbook.context;
359
357
 
360
358
  if (!(oWorkbookContext instanceof Object)) {
361
359
  oWorkbookContext = mParameters.workbook.context = {};
@@ -368,14 +366,13 @@ sap.ui.define([
368
366
  }
369
367
 
370
368
  /* Initialize currency customizing for currencies and units of measure */
371
- mCurrencySettings = mParameters.customizing.currency = {};
372
- mUnitSettings = mParameters.customizing.unit = {};
369
+ const mCurrencySettings = mParameters.customizing.currency = {};
370
+ const mUnitSettings = mParameters.customizing.unit = {};
371
+ const oCustomCurrencies = Formatting.getCustomCurrencies();
373
372
 
374
373
  /* Attach custom currency configuration */
375
- var oCustomCurrencies = Formatting.getCustomCurrencies();
376
-
377
374
  if (oCustomCurrencies) {
378
- for (sCurrencyCode in oCustomCurrencies) {
375
+ for (const sCurrencyCode in oCustomCurrencies) {
379
376
  addUnit(sCurrencyCode, oCustomCurrencies[sCurrencyCode], mCurrencySettings);
380
377
  }
381
378
  }
@@ -509,13 +506,11 @@ sap.ui.define([
509
506
  * @private
510
507
  */
511
508
  Spreadsheet.prototype.onprogress = function(iFetched, iTotal) {
512
- var iProgress;
513
-
514
509
  if (isNaN(iFetched) || isNaN(iTotal)) {
515
510
  return;
516
511
  }
517
512
 
518
- iProgress = Math.round(iFetched / iTotal * 100);
513
+ const iProgress = Math.round(iFetched / iTotal * 100);
519
514
  Log.debug('Spreadsheet export: ' + iProgress + '% loaded.');
520
515
  };
521
516
 
@@ -532,13 +527,13 @@ sap.ui.define([
532
527
  * Use empty array as default in case of <code>ListBinding</code> is not of type
533
528
  * ClientListBinding and does not provide a getDownloadUrl function
534
529
  */
535
- var oDataSource = [];
530
+ let oDataSource = [];
536
531
 
537
532
  /**
538
533
  * If <code>ClientListBinding</code>, we use the binding path to receive the data from the underlying model
539
534
  */
540
535
  if (oBinding.isA('sap.ui.model.ClientListBinding')) {
541
- var aData = [];
536
+ const aData = [];
542
537
 
543
538
  oBinding.getAllCurrentContexts().forEach(function(oContext) {
544
539
  aData.push(oContext.getObject());
@@ -558,10 +553,10 @@ sap.ui.define([
558
553
  * All other <code>Bindings</code> need to provide a downloadUrl
559
554
  */
560
555
  if (typeof oBinding.getDownloadUrl === 'function') {
561
- var oModel = oBinding.getModel(),
562
- sDataUrl = oBinding.getDownloadUrl('json'),
563
- sServiceUrl = oModel.sServiceUrl,
564
- bV4ODataModel = oModel.isA('sap.ui.model.odata.v4.ODataModel');
556
+ const oModel = oBinding.getModel();
557
+ const bV4ODataModel = oModel.isA('sap.ui.model.odata.v4.ODataModel');
558
+ let sDataUrl = oBinding.getDownloadUrl('json');
559
+ let sServiceUrl = oModel.sServiceUrl;
565
560
 
566
561
  sDataUrl = ExportUtils.interceptUrl(sDataUrl);
567
562
  sServiceUrl = ExportUtils.interceptUrl(sServiceUrl);
@@ -603,8 +598,8 @@ sap.ui.define([
603
598
  * @public
604
599
  */
605
600
  Spreadsheet.prototype.processDataSource = function(oDataSource) {
606
- var mDataSource = null;
607
- var sDataSourceType = typeof oDataSource;
601
+ const sDataSourceType = typeof oDataSource;
602
+ let mDataSource = null;
608
603
 
609
604
  if (!oDataSource) {
610
605
  return null;
@@ -649,17 +644,17 @@ sap.ui.define([
649
644
  * @private
650
645
  */
651
646
  Spreadsheet.prototype.createBuildPromise = function(mParameters) {
652
- var that = this;
647
+ const that = this;
653
648
 
654
649
  return new Promise(function(fnResolve, fnReject) {
655
650
 
656
- var progressDialog;
657
- var MAX_ROWS = 1048576; // Maximum allowed Rows per sheet
651
+ let progressDialog;
652
+ const MAX_ROWS = 1_048_576; // Maximum allowed Rows per sheet
658
653
  const iCount = mParameters.dataSource.count;
659
654
  const iDownloadLimit = mParameters.dataSource.downloadLimit;
660
- var nSizeLimit = Device.system.desktop ? 2000000 : 100000; // 2.000.000 cells on desktop and 100.000 otherwise
661
- var nRows = mParameters.dataSource.type == 'array' ? mParameters.dataSource.data.length : iDownloadLimit || iCount;
662
- var nColumns = mParameters.workbook.columns.length;
655
+ const nSizeLimit = Device.system.desktop ? 2_000_000 : 100_000; // 2.000.000 cells on desktop and 100.000 otherwise
656
+ const nRows = mParameters.dataSource.type == 'array' ? mParameters.dataSource.data.length : iDownloadLimit || iCount;
657
+ const nColumns = mParameters.workbook.columns.length;
663
658
 
664
659
  function onmessage(oMessage) {
665
660
 
@@ -706,7 +701,7 @@ sap.ui.define([
706
701
  }
707
702
 
708
703
  if (typeof oMessage.error != 'undefined') {
709
- var sError = oMessage.error.message || oMessage.error;
704
+ const sError = oMessage.error.message || oMessage.error;
710
705
  that.process = null;
711
706
 
712
707
  if (progressDialog) {
@@ -762,7 +757,7 @@ sap.ui.define([
762
757
  // Consider showing a dialog to the end users instead of just this error!
763
758
  fnReject('No columns to export.');
764
759
  } else if (nRows * nColumns > nSizeLimit || !nRows || nRows >= MAX_ROWS || isDownloadLimitLessThanCount(nRows, iCount)) { // Amount of rows need to be less than maximum amount because of column header
765
- var oDialogSettings = {
760
+ const oDialogSettings = {
766
761
  rows: nRows,
767
762
  columns: nColumns,
768
763
  cellLimit: nSizeLimit,
@@ -21,7 +21,7 @@ sap.ui.define(['sap/base/Log', 'sap/ui/export/ExportUtils'], function(Log, Expor
21
21
  * Utility class to perform spreadsheet export.
22
22
  *
23
23
  * @author SAP SE
24
- * @version 1.130.0
24
+ * @version 1.131.0
25
25
  *
26
26
  * @alias sap.ui.export.SpreadsheetExport
27
27
  * @private
@@ -15,7 +15,7 @@ sap.ui.define(["sap/ui/core/Lib"], function(Library) {
15
15
  * @namespace
16
16
  * @alias sap.ui.export
17
17
  * @author SAP SE
18
- * @version 1.130.0
18
+ * @version 1.131.0
19
19
  * @public
20
20
  */
21
21
 
@@ -33,7 +33,7 @@ sap.ui.define(["sap/ui/core/Lib"], function(Library) {
33
33
  interfaces: [],
34
34
  controls: [],
35
35
  elements: [],
36
- version: "1.130.0"
36
+ version: "1.131.0"
37
37
  });
38
38
 
39
39
  /**
@@ -4,18 +4,18 @@
4
4
  */
5
5
 
6
6
  (function(fClass) {
7
- 'use strict';
7
+ "use strict";
8
8
 
9
- if (typeof globalThis.sap?.ui?.define === 'function') {
9
+ if (typeof globalThis.sap?.ui?.define === "function") {
10
10
  globalThis.sap.ui.define([], fClass, /* bExport */ true);
11
11
  } else {
12
12
  globalThis.DataProviderBase = fClass();
13
13
  }
14
14
  })(function() {
15
- 'use strict';
15
+ "use strict";
16
16
 
17
17
  // eslint-disable-next-line
18
- /* global URL, XMLHttpRequest */
18
+ /* global URL */
19
19
 
20
20
  /**
21
21
  * Default DataProviderBase implementation that is capable to handle
@@ -24,7 +24,7 @@
24
24
  * @param {object} mSettings Data service related part of the export configuration
25
25
  *
26
26
  * @author SAP SE
27
- * @version 1.130.0
27
+ * @version 1.131.0
28
28
  *
29
29
  * @constructor
30
30
  * @class DataProviderBase
@@ -32,42 +32,41 @@
32
32
  * @since 1.77
33
33
  * @private
34
34
  */
35
- var DataProviderBase = function(mSettings) {
35
+ const DataProviderBase = function(mSettings) {
36
36
 
37
37
  this.mSettings = mSettings;
38
38
  this.bCanceled = false;
39
39
  this.iAvailableRows = 0;
40
40
  this.mRequest = null;
41
41
  this.iCount = Math.min(mSettings.dataSource.count || DataProviderBase.MAX_ROWS, DataProviderBase.MAX_ROWS);
42
-
43
- if (this.mSettings.dataSource.downloadLimit) {
44
- this.iTotalRows = this.mSettings.dataSource.downloadLimit;
45
- } else {
46
- this.iTotalRows = DataProviderBase.MAX_ROWS;
47
- }
48
-
42
+ this.iTotalRows = mSettings.dataSource.downloadLimit ?? DataProviderBase.MAX_ROWS;
49
43
  this.iBatchSize = Math.min(mSettings.dataSource.sizeLimit || DataProviderBase.MAX_ROWS, this.iTotalRows);
44
+ this.fnConvertData = DataProviderBase.getDataConverter(mSettings);
50
45
 
51
46
  this._prepareDataUrl();
52
47
  };
53
48
 
54
49
  DataProviderBase.MAX_ROWS = 1048575; // Spreadsheet limit minus 1 for the header row: 1,048,575
55
- DataProviderBase.HTTP_ERROR_MSG = 'HTTP connection error';
56
- DataProviderBase.HTTP_WRONG_RESPONSE_MSG = 'Unexpected server response:\n';
50
+ DataProviderBase.HTTP_ERROR_MSG = "HTTP connection error";
51
+ DataProviderBase.HTTP_WRONG_RESPONSE_MSG = "Unexpected server response:\n";
57
52
 
58
53
  /**
59
54
  * Creates a pseudo random GUID. This algorithm is not suitable for
60
- * cryptographic purposes and should not be used therefore.
55
+ * cryptographic purposes and should not be used for such.
61
56
  *
62
- * @returns {string} - Generated GUID
57
+ * @returns {string} Generated GUID
63
58
  *
64
59
  * @static
65
60
  * @private
66
61
  */
67
62
  DataProviderBase._createGuid = function() {
68
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
69
- var r = Math.random() * 16 | 0, // Bitwise OR is equivalent to Math.floor() but faster
70
- v = c === 'x' ? r : ((r & 0x3) | 0x8); // In case of c != 'x', the value is always between 0x8 and 0xB
63
+ if (globalThis.crypto?.randomUUID) {
64
+ return globalThis.crypto.randomUUID();
65
+ }
66
+
67
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
68
+ const r = Math.random() * 16 | 0, // Bitwise OR is equivalent to Math.floor() but faster
69
+ v = c === "x" ? r : ((r & 0x3) | 0x8); // In case of c != "x", the value is always between 0x8 and 0xB
71
70
 
72
71
  return v.toString(16);
73
72
  });
@@ -77,18 +76,17 @@
77
76
  * The function returns array of columns that need special conversion for values.
78
77
  * E.g. handling data from association/navigationProperty
79
78
  *
80
- * @param {Array} aColumns - Configuration object
81
- * @returns {Array} - Collection of columns that need special conversion for their values
79
+ * @param {Array} aColumns Configuration object
80
+ * @returns {Array} Collection of columns that need special conversion for their values
82
81
  *
83
82
  * @static
84
83
  * @private
85
84
  */
86
85
  DataProviderBase.getColumnsToConvert = function(aColumns) {
87
86
  return aColumns.reduce(function(result, col) {
88
- var properties;
89
87
 
90
88
  // Handle aggregated properties and single properties
91
- properties = col.property instanceof Array ? col.property : [col.property];
89
+ const properties = col.property instanceof Array ? col.property : [col.property];
92
90
  // Handle unitProperty which too could be from an association
93
91
  if (col.unitProperty) {
94
92
  properties.push(col.unitProperty);
@@ -97,7 +95,7 @@
97
95
  properties.forEach(function(property) {
98
96
 
99
97
  // Convert navigation property and date fields
100
- var aKeys = property.split('/');
98
+ const aKeys = property.split("/");
101
99
 
102
100
  if (aKeys.length > 1) {
103
101
  result.push({
@@ -122,9 +120,7 @@
122
120
  * @public
123
121
  */
124
122
  DataProviderBase.getDataConverter = function(mSettings) {
125
- var aColumns, aColumnsToConvert;
126
-
127
- aColumns = mSettings.workbook.columns;
123
+ let aColumns = mSettings.workbook.columns;
128
124
 
129
125
  /* Add hierarachyLevel as virtual column for NavigationProperty conversion */
130
126
  if (mSettings.workbook.hierarchyLevel) {
@@ -133,7 +129,7 @@
133
129
  }]);
134
130
  }
135
131
 
136
- aColumnsToConvert = this.getColumnsToConvert(aColumns);
132
+ const aColumnsToConvert = this.getColumnsToConvert(aColumns);
137
133
 
138
134
  return function(aRows) {
139
135
  return DataProviderBase._convertData(aRows, aColumnsToConvert);
@@ -143,9 +139,9 @@
143
139
  /**
144
140
  * Function to process the JSON result array from a ODataService.
145
141
  *
146
- * @param {Array} aRows - Data array that contains the received data
147
- * @param {Array} aCols - Columns that need to be converted
148
- * @returns {Array} - An array of rows
142
+ * @param {Array} aRows Data array that contains the received data
143
+ * @param {Array} aCols Columns that need to be converted
144
+ * @returns {Array} An array of rows
149
145
  *
150
146
  * @static
151
147
  * @private
@@ -164,10 +160,10 @@
164
160
  * Gets converted property value from raw data.
165
161
  * Navigation properties are parsed.
166
162
  *
167
- * @param {object} oRow - Raw data row
168
- * @param {object} oCol - Column information
169
- * @param {Array} oCol.keys - Property name or key path for navigation properties
170
- * @returns {number|string|boolean} - The converted property value
163
+ * @param {object} oRow Raw data row
164
+ * @param {object} oCol Column information
165
+ * @param {Array} oCol.keys Property name or key path for navigation properties
166
+ * @returns {number|string|boolean} The converted property value
171
167
  *
172
168
  * @static
173
169
  * @private
@@ -175,7 +171,7 @@
175
171
  DataProviderBase._getValue = function(oRow, oCol) {
176
172
 
177
173
  // Get property value
178
- var value = oCol.keys.reduce(function(obj, key) {
174
+ const value = oCol.keys.reduce(function(obj, key) {
179
175
  return obj && obj[key];
180
176
  }, oRow);
181
177
 
@@ -186,23 +182,22 @@
186
182
  * The function requests several chunks of data until the maximum
187
183
  * amount of data is fetched.
188
184
  *
189
- * @param {function} fnProcessCallback - Callback function that is triggered when data is received
190
- * @returns {object} - Object reference that allows to cancel the current processing
185
+ * @param {function} fnProcessCallback Callback function that is triggered when data is received
186
+ * @returns {object} Object reference that allows to cancel the current processing
191
187
  *
192
188
  * @since 1.77
193
189
  * @public
194
190
  */
195
191
  DataProviderBase.prototype.requestData = function(fnProcessCallback) {
196
- var mDataSource = this.mSettings.dataSource;
192
+ const mDataSource = this.mSettings.dataSource;
197
193
 
198
- this.fnConvertData = DataProviderBase.getDataConverter(this.mSettings);
199
194
  this.fnProcessCallback = fnProcessCallback;
200
195
 
201
196
  // Execution
202
197
  this.mRequest = {
203
198
  serviceUrl: this._cleanUrl(mDataSource.serviceUrl),
204
199
  dataUrl: this._getUrl(0, this.iBatchSize),
205
- method: mDataSource.useBatch ? 'BATCH' : 'GET',
200
+ useBatch: mDataSource.useBatch,
206
201
  headers: mDataSource.headers
207
202
  };
208
203
 
@@ -214,8 +209,8 @@
214
209
  cancel: function() {
215
210
  this.bCanceled = true;
216
211
 
217
- if (this.oPendingXHR instanceof XMLHttpRequest) {
218
- this.oPendingXHR.abort();
212
+ if (this._oPendingRequest instanceof AbortController) {
213
+ this._oPendingRequest.abort();
219
214
  }
220
215
  }.bind(this)
221
216
  };
@@ -226,36 +221,32 @@
226
221
  * the data before executing the callback function allows to
227
222
  * apply transformations to the data.
228
223
  *
229
- * @param {object} oResult - The result object that is provided by the Promise resolve.
224
+ * @param {Response} oResponse The <code>Response</code> object that was received
230
225
  *
226
+ * @async
231
227
  * @private
232
228
  */
233
- DataProviderBase.prototype.fnOnDataReceived = function(oResult) {
234
- var aData, sNextUrl, iFetchedRows, iRemainingRows;
235
- var mCallbackParams = {};
236
- this.oPendingXHR = null;
229
+ DataProviderBase.prototype.fnOnDataReceived = async function(oResponse) {
230
+ this._oPendingRequest = null;
231
+
237
232
  if (this.bCanceled) {
238
- return; // Canceled by the application
233
+ return; // Cancelled by the application
239
234
  }
240
235
 
241
- /* Check for OData V4 result, if not present check for OData V2 result or apply default */
242
- aData = (oResult && oResult.value || (oResult.d && (oResult.d.results || oResult.d))) || oResult;
243
- aData = (Array.isArray(aData)) ? aData : [];
244
- iFetchedRows = aData.length;
236
+ const {data: aData, nextUrl: sNextUrl} = await DataProviderBase.getHarmonizedBodyFrom(oResponse);
237
+ const bWasServerSidePaging = this.mRequest?.dataUrl.includes("$skiptoken");
238
+ const iFetchedRows = aData.length;
245
239
 
246
240
  this.iAvailableRows += iFetchedRows;
247
241
 
248
- // Check if next url is provided
249
- sNextUrl = (oResult && oResult['@odata.nextLink'] || (oResult.d && oResult.d.__next)) || null;
250
-
251
- iRemainingRows = this.iTotalRows - this.iAvailableRows;
242
+ const iRemainingRows = this.iTotalRows - this.iAvailableRows;
243
+ const mCallbackParams = {};
252
244
 
253
- mCallbackParams.finished = this._isFinished(iFetchedRows, sNextUrl, iRemainingRows);
245
+ mCallbackParams.finished = this._isFinished(iFetchedRows, sNextUrl, iRemainingRows, bWasServerSidePaging);
254
246
  mCallbackParams.progress = this.iTotalRows;
255
247
  mCallbackParams.total = this.iTotalRows < this.iCount ? this.iTotalRows : this.iCount;
256
248
  mCallbackParams.fetched = this.iAvailableRows;
257
249
 
258
-
259
250
  if (!mCallbackParams.finished) {
260
251
  // Trigger next page request before processing received data. Fetch only configured/max limit rows
261
252
  this.mRequest.dataUrl = this._getUrl(this.iAvailableRows, Math.min(this.iBatchSize, iRemainingRows), sNextUrl);
@@ -272,26 +263,54 @@
272
263
  };
273
264
 
274
265
  /**
275
- * The function returns array of columns that need special conversion for values.
276
- * E.g. handling data from association/navigationProperty
277
- *
278
- * @param {number} iFetchedRows - Number of rows fetched
279
- * @param {string} sNextUrl - Next url to fetch data
280
- * @param {number} iRemainingRows - Remaining rows to fetch
281
- * @returns {boolean} - True if the finish condition is met
282
- *
283
- * @static
266
+ * The function checks if the finish condition is met. This depends on
267
+ * multiple parameters like the amount of fetched rows, the next URL,
268
+ * the remaining rows and if server side paging was used.
269
+ *
270
+ * If the request was fulfilled, the function returns <code>false</code>
271
+ * to allow fetching additional data. If no data was fetched, the function
272
+ * returns <code>true</code>. If the remaining rows are less or equal to
273
+ * zero, the function returns <code>true</code>. If next URL is provided
274
+ * or server side paging was used while less data than expected was
275
+ * returned, the function returns <code>false</code>.
276
+ *
277
+ * If none of the beforementioned conditions are met, the function returns
278
+ * <code>true</code>.
279
+ *
280
+ * @param {number} iFetchedRows Number of rows fetched
281
+ * @param {string} sNextUrl Next url to fetch data
282
+ * @param {number} iRemainingRows Remaining rows to fetch
283
+ * @param {boolean} bWasServerSidePaging Flag if server side paging was used
284
+ *
285
+ * @returns {boolean} True if the finish condition is met
284
286
  * @private
285
287
  */
286
- DataProviderBase.prototype._isFinished = function(iFetchedRows, sNextUrl, iRemainingRows) {
287
- const bFetchedRows = !sNextUrl && ((iFetchedRows < this.iBatchSize) || iFetchedRows > this.iBatchSize);
288
- return iFetchedRows === 0 || iRemainingRows <= 0 || bFetchedRows;
288
+ DataProviderBase.prototype._isFinished = function(iFetchedRows, sNextUrl, iRemainingRows, bWasServerSidePaging) {
289
+ // Most common scenario - request gets fulfilled
290
+ if (iFetchedRows === this.iBatchSize) {
291
+ return false;
292
+ }
293
+
294
+ if (iFetchedRows === 0 || iRemainingRows <= 0) {
295
+ return true;
296
+ }
297
+
298
+ if (sNextUrl) {
299
+ return false;
300
+ }
301
+
302
+ // If server side paging was used, the data chunks can be smaller than the batch size
303
+ if (iFetchedRows < this.iBatchSize && bWasServerSidePaging) {
304
+ return false;
305
+ }
306
+
307
+ return true;
289
308
  };
290
309
 
291
310
  /**
292
311
  * Inner function that processes request handler exceptions.
293
312
  *
294
- * @param {string} sMessage - Error message.
313
+ * @param {string} sMessage Error message.
295
314
  *
296
315
  * @private
297
316
  */
@@ -304,22 +323,20 @@
304
323
  /**
305
324
  * Nested function to remove not used information from the URL
306
325
  *
307
- * @param {string} sUrl - A URL that may contain a path, hash and request parameters
308
- * @returns {string} - A clean URL
326
+ * @param {string} sUrl A URL that may contain a path, hash and request parameters
309
327
  *
328
+ * @returns {string} A clean URL
310
329
  * @private
311
330
  */
312
331
  DataProviderBase.prototype._cleanUrl = function(sUrl) {
313
- var oURL;
314
-
315
332
  if (!sUrl) {
316
- return '';
333
+ return "";
317
334
  }
318
335
 
319
- oURL = new URL(sUrl);
336
+ const oURL = new URL(sUrl);
320
337
 
321
- oURL.hash = oURL.search = '';
322
- oURL.pathname += oURL.pathname.endsWith('/') ? '' : '/';
338
+ oURL.hash = oURL.search = "";
339
+ oURL.pathname += oURL.pathname.endsWith("/") ? "" : "/";
323
340
 
324
341
  return oURL.toString();
325
342
  };
@@ -330,25 +347,26 @@
330
347
  * @private
331
348
  */
332
349
  DataProviderBase.prototype._prepareDataUrl = function() {
333
- var mDataSource = this.mSettings.dataSource;
334
- var mDataUrl, reSkip = /\$skip\=[0-9]+/, reTop = /\$top\=[0-9]+/;
350
+ const mDataSource = this.mSettings.dataSource;
351
+ const reSkip = /\$skip\=[0-9]+/, reTop = /\$top\=[0-9]+/;
335
352
 
336
353
  if (!mDataSource.dataUrl) {
337
- throw 'Unable to load data - no URL provided.';
354
+ throw "Unable to load data - no URL provided.";
338
355
  }
339
356
 
340
- mDataUrl = new URL(mDataSource.dataUrl);
341
- mDataUrl.search = mDataUrl.search || '';
357
+ const mDataUrl = new URL(mDataSource.dataUrl);
358
+
359
+ mDataUrl.search = mDataUrl.search || "";
342
360
 
343
361
  // Add missing $skip if needed
344
362
  if (!reSkip.test(mDataUrl.search)) {
345
363
  // Apply $skip with some numeric dummy value that matches the regexp in DataProviderBase#_getUrl
346
- mDataUrl.search += (mDataUrl.search.length ? '&$skip=' : '$skip=') + 0;
364
+ mDataUrl.search += (mDataUrl.search.length ? "&$skip=" : "$skip=") + 0;
347
365
  }
348
366
  // Add missing $top if needed
349
367
  if (!reTop.test(mDataUrl.search)) {
350
368
  // Apply $top with some numeric dummy value that matches the regexp in DataProviderBase#_getUrl
351
- mDataUrl.search += '&$top=' + 0;
369
+ mDataUrl.search += "&$top=" + 0;
352
370
  }
353
371
 
354
372
  this.mSettings.dataSource.dataUrl = mDataUrl.toString();
@@ -357,17 +375,15 @@
357
375
  /**
358
376
  * Creates the download URL for the next query.
359
377
  *
360
- * @param {number} iSkip - The amount of items that are already present and will be skipped
361
- * @param {number} iTop - The amount of items that should be requested with this query
362
- * @param {string} [sNextUrl] - A reference to the next bulk of data that was returned by the previous request
363
- * @returns {string} - The URL for the next query
378
+ * @param {number} iSkip The amount of items that are already present and will be skipped
379
+ * @param {number} iTop The amount of items that should be requested with this query
380
+ * @param {string} [sNextUrl] A reference to the next bulk of data that was returned by the previous request
364
381
  *
382
+ * @returns {string} The URL for the next query
365
383
  * @private
366
384
  */
367
385
  DataProviderBase.prototype._getUrl = function(iSkip, iTop, sNextUrl) {
368
- var oDataUrl, oNextUrl;
369
-
370
- oDataUrl = new URL(this.mSettings.dataSource.dataUrl);
386
+ const oDataUrl = new URL(this.mSettings.dataSource.dataUrl);
371
387
 
372
388
  /*
373
389
  * Use $skiptoken from response to query the next items.
@@ -378,185 +394,243 @@
378
394
  */
379
395
  if (sNextUrl) {
380
396
  // sNextUrl can be relative, therefore we need to apply a base even though it is not used
381
- oNextUrl = new URL(sNextUrl, oDataUrl.origin);
397
+ const oNextUrl = new URL(sNextUrl, oDataUrl.origin);
398
+
382
399
  oDataUrl.search = oNextUrl.search;
383
400
  } else { // Use $skip and $top
384
- oDataUrl.search = (oDataUrl.search || '')
385
- .replace(/\$skip\=[0-9]+/g, '$skip=' + iSkip)
386
- .replace(/\$top\=[0-9]+/g, '$top=' + iTop);
401
+ oDataUrl.search = (oDataUrl.search || "")
402
+ .replace(/\$skip\=[0-9]+/g, "$skip=" + iSkip)
403
+ .replace(/\$top\=[0-9]+/g, "$top=" + iTop);
387
404
  }
388
405
 
389
406
  return oDataUrl.toString();
390
407
  };
391
408
 
392
409
  /**
393
- * This method creates an XMLHttpRequest from the provided
394
- * configuration and requests the data from the backend. The
395
- * configuration is configured to use OData services.
396
- *
397
- * @param {object} oRequest - Request configuration object
398
- * @param {string} oRequest.method - References the HTTP method that is used (default: GET)
399
- * @param {string} oRequest.dataUrl - References the resource URL that gets invoked
400
- * @param {string} oRequest.serviceUrl - References the service URL that gets invoked
401
- * @return {Promise} Returns a Promise that will be resolve once the requested data was fetched
410
+ * This method creates a fetch Request from the provided configuration and
411
+ * requests the data from the backend. If the data is requested via $batch
412
+ * the corresponding Reponse is unwrapped and returned. This allows direct
413
+ * access to the result of the inner request and provides a harmonized API
414
+ * for consuming the data.
402
415
  *
416
+ * @param {object} mSettings Request configuration object
417
+ * @param {string} mSettings.useBatch Defines if the data is requested via GET or $batch request
418
+ * @param {string} mSettings.dataUrl References the resource URL that gets invoked
419
+ * @param {string} mSettings.serviceUrl References the service URL that gets invoked
420
+ *
421
+ * @return {Promise} Returns a Promise that will be resolve once the requested data was fetched
422
+ * @async
403
423
  * @private
404
424
  */
405
- DataProviderBase.prototype.sendRequest = function(oRequest) {
406
- var fnSendRequest;
425
+ DataProviderBase.prototype.sendRequest = async function(mSettings) {
426
+ if (typeof mSettings !== "object" || mSettings === null || typeof mSettings.dataUrl !== "string") {
427
+ throw new Error("Unable to send request - Mandatory parameters missing.");
428
+ }
407
429
 
408
- if (typeof oRequest !== 'object' || oRequest === null || typeof oRequest.dataUrl !== 'string') {
409
- throw new Error('Unable to send request - Mandatory parameters missing.');
430
+ const bBatchRequest = mSettings.useBatch && mSettings.serviceUrl;
431
+ const oAbortController = new AbortController();
432
+ const oRequest = bBatchRequest ?
433
+ DataProviderBase.createBatchRequest(mSettings, oAbortController) : DataProviderBase.createGetRequest(mSettings, oAbortController);
434
+ let oResponse;
435
+
436
+ try {
437
+ this._oPendingRequest = oAbortController;
438
+ oResponse = await fetch(oRequest);
439
+ } catch (oError) {
440
+ this._oPendingRequest = null;
441
+
442
+ if (oError.name === "AbortError") {
443
+ throw null; // Explicitly reject the Promise without an error to indicate user cancellation
444
+ } else {
445
+ throw oError; // Technical request error
446
+ }
410
447
  }
411
448
 
412
- fnSendRequest = (oRequest.method === 'BATCH' && oRequest.serviceUrl ? this.sendBatchRequest : this.sendGetRequest).bind(this);
449
+ oResponse = bBatchRequest ? await DataProviderBase.getBatchResponse(oResponse) : oResponse;
413
450
 
414
- return fnSendRequest(oRequest);
451
+ if (!oResponse.ok) {
452
+ const sMessage = await oResponse.text();
453
+ throw new Error(DataProviderBase.HTTP_WRONG_RESPONSE_MSG + sMessage);
454
+ }
455
+
456
+ return oResponse;
415
457
  };
416
458
 
417
459
  /**
418
- * Creates a $batch request and sends it to the backend service.
460
+ * Creates a <code>Request</code> object for an OData GET request.
419
461
  *
420
- * @param {object} oRequest - Request object that contains all necessary information to create the batch request
421
- * @returns {Promise} - A Promise that resolves in a JSON object containing the fetched data
462
+ * @param {object} mSettings Request configuration
463
+ * @param {string} mSettings.dataUrl References the resource URL that gets invoked
464
+ * @param {AbortController} oAbortController AbortController instance to cancel the request
422
465
  *
466
+ * @returns {Request} The request object
467
+ * @static
423
468
  * @private
424
469
  */
425
- DataProviderBase.prototype.sendBatchRequest = function(oRequest) {
426
- return new Promise(function(fnResolve, fnReject) {
427
- var xhr = new XMLHttpRequest();
428
- var boundary = 'batch_' + DataProviderBase._createGuid();
429
- var getUrl = oRequest.dataUrl.split(oRequest.serviceUrl)[1];
430
- var body = [];
431
- var sKey, sValue;
432
-
433
- xhr.onload = function() {
434
- var responseText, aLines, iEnd, iLength, iStart, sHttpStatus, aMatch;
435
-
436
- aLines = this.responseText.split('\r\n');
437
-
438
- // TBD: check return codes
439
- iStart = 0;
440
- iLength = aLines.length;
441
- iEnd = iLength - 1;
442
-
443
- aLines.forEach(function(sLine) {
444
- aMatch = sLine.match(/^HTTP\/1\.[0|1] ([1-9][0-9]{2})/);
445
-
446
- if (Array.isArray(aMatch) && aMatch[1]) {
447
- sHttpStatus = aMatch[1];
448
- }
449
- });
450
-
451
- while (iStart < iLength && aLines[iStart].slice(0, 1) !== '{') {
452
- iStart++;
453
- }
470
+ DataProviderBase.createGetRequest = function(mSettings, oAbortController) {
471
+ const oHeaders = new Headers(mSettings.headers);
454
472
 
455
- while (iEnd > 0 && aLines[iEnd].slice(-1) !== '}') {
456
- iEnd--;
457
- }
458
- aLines = aLines.slice(iStart, iEnd + 1);
459
- responseText = aLines.join('\r\n');
473
+ oHeaders.set("Accept", "application/json");
460
474
 
461
- if (sHttpStatus && parseInt(sHttpStatus) >= 400) {
462
- // Provide a fallback in case the responseText is empty
463
- fnReject(DataProviderBase.HTTP_WRONG_RESPONSE_MSG + responseText);
475
+ return new Request(mSettings.dataUrl, {
476
+ cache: "no-store",
477
+ headers: oHeaders,
478
+ signal: oAbortController.signal
479
+ });
480
+ };
464
481
 
465
- return;
466
- }
482
+ /**
483
+ * Creates a <code>Request</code> object for an OData $batch request.
484
+ *
485
+ * @param {object} mSettings Request configuration
486
+ * @param {string} mSettings.dataUrl References the resource URL that gets invoked
487
+ * @param {string} mSettings.serviceUrl References the service URL that gets invoked
488
+ * @param {AbortController} oAbortController AbortController instance to cancel the request
489
+ *
490
+ * @returns {Request} The request object
491
+ * @static
492
+ * @private
493
+ */
494
+ DataProviderBase.createBatchRequest = function(mSettings, oAbortController) {
495
+ const sUrl = mSettings.dataUrl.split(mSettings.serviceUrl)[1];
496
+ const sBoundary = "batch_" + DataProviderBase._createGuid();
497
+ const oHeaders = new Headers(mSettings.headers);
498
+ const sBody = DataProviderBase.createRequestBody(sUrl, sBoundary, oHeaders);
499
+
500
+ oHeaders.set("Accept", "multipart/mixed");
501
+ oHeaders.set("Content-Type", "multipart/mixed;boundary=" + sBoundary);
502
+
503
+ return new Request(mSettings.serviceUrl + "$batch", {
504
+ body: sBody,
505
+ cache: "no-store",
506
+ headers: oHeaders,
507
+ method: "POST",
508
+ signal: oAbortController.signal
509
+ });
510
+ };
467
511
 
468
- try {
469
- fnResolve(JSON.parse(responseText));
470
- } catch (e) {
471
- fnReject(DataProviderBase.HTTP_WRONG_RESPONSE_MSG + responseText);
472
- }
473
- };
512
+ /**
513
+ * Creates the request body for a $batch request.
514
+ *
515
+ * @param {string} sUrl The URL that is requested
516
+ * @param {string} sBoundary The boundary id for the inner request
517
+ * @param {Headers} oHeaders The headers that are sent with the request
518
+ *
519
+ * @returns {string} The request body
520
+ * @static
521
+ * @private
522
+ */
523
+ DataProviderBase.createRequestBody = function(sUrl, sBoundary, oHeaders) {
524
+ const aBody = [];
474
525
 
475
- /* Handle technical request errors like timeout, disconnect, etc. */
476
- xhr.onerror = function() {
477
- fnReject(this.responseText || DataProviderBase.HTTP_ERROR_MSG);
478
- };
526
+ aBody.push("--" + sBoundary);
527
+ aBody.push("Content-Type: application/http");
528
+ aBody.push("Content-Transfer-Encoding: binary");
529
+ aBody.push("");
530
+ aBody.push(`GET ${sUrl} HTTP/1.1`);
479
531
 
480
- // Create request
481
- xhr.open('POST', oRequest.serviceUrl + '$batch', true);
532
+ /* Set header information */
533
+ aBody.push("Accept:application/json");
482
534
 
483
- xhr.setRequestHeader('Accept', 'multipart/mixed');
484
- xhr.setRequestHeader('Content-Type', 'multipart/mixed;boundary=' + boundary);
535
+ for (const [sKey, sValue] of oHeaders.entries()) {
485
536
 
486
- body.push('--' + boundary);
487
- body.push('Content-Type: application/http');
488
- body.push('Content-Transfer-Encoding: binary');
489
- body.push('');
490
- body.push('GET ' + getUrl + ' HTTP/1.1');
537
+ if (sKey != "accept") {
538
+ aBody.push(sKey + ":" + sValue);
539
+ }
540
+ }
491
541
 
492
- /* Set header information on the request as well as on the batch request */
493
- body.push('Accept:application/json');
542
+ aBody.push("");
543
+ aBody.push("");
544
+ aBody.push("--" + sBoundary + "--");
545
+ aBody.push("");
494
546
 
495
- for (sKey in oRequest.headers) {
496
- sValue = oRequest.headers[sKey];
547
+ return aBody.join("\r\n");
548
+ };
497
549
 
498
- if (sKey.toLowerCase() != 'accept') {
499
- xhr.setRequestHeader(sKey, sValue);
500
- body.push(sKey + ':' + sValue);
501
- }
502
- }
550
+ /**
551
+ * Extracts the response body from a $batch request and returns
552
+ * it as <code>Response</code> object.
553
+ *
554
+ * @param {Response} oResponse The response object that contains the $batch response
555
+ *
556
+ * @returns {Response} The response object for the inner request
557
+ * @static
558
+ * @async
559
+ * @private
560
+ */
561
+ DataProviderBase.getBatchResponse = async function(oResponse) {
562
+ const sResponseBody = await oResponse.text();
563
+
564
+ if (!oResponse.ok) {
565
+ throw new Error(DataProviderBase.HTTP_WRONG_RESPONSE_MSG + sResponseBody);
566
+ }
567
+
568
+ const aLines = sResponseBody.split("\r\n");
569
+
570
+ // Remove all lines prior to HTTP status
571
+ aLines.splice(0, aLines.findIndex((sLine) => /^HTTP\/1\.[0|1] ([1-9][0-9]{2})/.test(sLine)));
503
572
 
504
- body.push('');
505
- body.push('');
506
- body.push('--' + boundary + '--');
507
- body.push('');
508
- body = body.join('\r\n');
509
- xhr.send(body);
573
+ const [, iStatusCode, sStatusText] = aLines[0].match(/^HTTP\/1\.[0|1] ([1-9][0-9]{2}) ?([\w ]*)$/);
574
+ const sBody = aLines.find((sLine) => sLine.startsWith("{") && sLine.endsWith("}"));
510
575
 
511
- this.oPendingXHR = xhr;
512
- }.bind(this));
576
+ const reHeaders = /^([\w\-]+): ?([\d\w\./]+)/;
577
+ const oHeaders = aLines
578
+ .filter((sLine) => reHeaders.test(sLine))
579
+ .reduce((oAccumulator, sLine) => {
580
+ const [, sKey, sValue] = sLine.match(reHeaders);
581
+
582
+ oAccumulator.set(sKey, sValue);
583
+
584
+ return oAccumulator;
585
+ }, new Headers());
586
+
587
+ return new Response(sBody, {
588
+ status: iStatusCode,
589
+ statusText: sStatusText,
590
+ headers: oHeaders
591
+ });
513
592
  };
514
593
 
515
594
  /**
516
- * Creates and sends a GET request to the backend service.
595
+ * Extracts the JSON data from the <code>Response</code> object and returns a harmonized result.
596
+ * It evaluates the result in the following order:
597
+ * 1. OData V4
598
+ * 2. OData V2
599
+ * 3. Default
600
+ *
601
+ * If the result is not an array, an empty array is returned.
517
602
  *
518
- * @param {object} oRequest - Request object that contains all necessary information to create the batch request
519
- * @returns {Promise} - A Promise that resolves in a JSON object containing the fetched data
603
+ * @param {Response} oResponse The response body that contains the data
520
604
  *
605
+ * @returns {Array} The response data as Array
606
+ * @static
607
+ * @async
521
608
  * @private
522
609
  */
523
- DataProviderBase.prototype.sendGetRequest = function(oRequest) {
524
- return new Promise(function(fnResolve, fnReject) {
525
- var sHeaderKey;
526
- var xhr = new XMLHttpRequest();
610
+ DataProviderBase.getHarmonizedBodyFrom = async function(oResponse) {
611
+ const oResult = {
612
+ data: []
613
+ };
527
614
 
528
- xhr.onload = function() {
529
- if (this.status >= 400) {
530
- // Provide a fallback in case the responseText is empty
531
- fnReject(this.responseText || this.statusText || DataProviderBase.HTTP_ERROR_MSG);
615
+ if (!oResponse) {
616
+ return oResult;
617
+ }
532
618
 
533
- return;
534
- }
535
- try {
536
- fnResolve(JSON.parse(this.responseText));
537
- } catch (e) {
538
- fnReject(DataProviderBase.HTTP_WRONG_RESPONSE_MSG + this.responseText);
539
- }
540
- };
619
+ const oBody = await oResponse.json();
541
620
 
542
- /* Handle technical request errors like timeout, disconnect, etc. */
543
- xhr.onerror = function() {
544
- fnReject(this.responseText || DataProviderBase.HTTP_ERROR_MSG);
545
- };
621
+ if (!oBody) {
622
+ return oResult;
623
+ }
546
624
 
547
- xhr.open('GET', oRequest.dataUrl, true);
548
- xhr.setRequestHeader('accept', 'application/json');
625
+ const aData = oBody.value || oBody.d?.results || oBody.d || oBody;
549
626
 
550
- /* Set custom header information on the request */
551
- for (sHeaderKey in oRequest.headers) {
552
- if (sHeaderKey.toLowerCase() !== 'accept') {
553
- xhr.setRequestHeader(sHeaderKey, oRequest.headers[sHeaderKey]);
554
- }
555
- }
627
+ oResult.nextUrl = oBody["@odata.nextLink"] ?? oBody.d?.__next;
628
+
629
+ if (Array.isArray(aData)) {
630
+ oResult.data = aData;
631
+ }
556
632
 
557
- xhr.send();
558
- this.oPendingXHR = xhr;
559
- }.bind(this));
633
+ return oResult;
560
634
  };
561
635
 
562
636
  return DataProviderBase;
@@ -20,7 +20,7 @@ sap.ui.define(['sap/ui/base/Object'], function(BaseObject) {
20
20
  * convenience functions like <code>sap.ui.export.util.Filter#setType</code> to improve the result.
21
21
  *
22
22
  * @author SAP SE
23
- * @version 1.130.0
23
+ * @version 1.131.0
24
24
  *
25
25
  * @since 1.110
26
26
  * @alias sap.ui.export.util.Filter