@sapui5/sap.ui.export 1.130.1 → 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.1",
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.1</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.1
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.1
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.1
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.1")
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.1
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.1")</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.1',
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.1
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.1
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.1
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.1"
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.1
27
+ * @version 1.131.0
28
28
  *
29
29
  * @constructor
30
30
  * @class DataProviderBase
@@ -32,43 +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
- this.fnConvertData = DataProviderBase.getDataConverter(this.mSettings);
43
-
44
- if (this.mSettings.dataSource.downloadLimit) {
45
- this.iTotalRows = this.mSettings.dataSource.downloadLimit;
46
- } else {
47
- this.iTotalRows = DataProviderBase.MAX_ROWS;
48
- }
49
-
42
+ this.iTotalRows = mSettings.dataSource.downloadLimit ?? DataProviderBase.MAX_ROWS;
50
43
  this.iBatchSize = Math.min(mSettings.dataSource.sizeLimit || DataProviderBase.MAX_ROWS, this.iTotalRows);
44
+ this.fnConvertData = DataProviderBase.getDataConverter(mSettings);
51
45
 
52
46
  this._prepareDataUrl();
53
47
  };
54
48
 
55
49
  DataProviderBase.MAX_ROWS = 1048575; // Spreadsheet limit minus 1 for the header row: 1,048,575
56
- DataProviderBase.HTTP_ERROR_MSG = 'HTTP connection error';
57
- 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";
58
52
 
59
53
  /**
60
54
  * Creates a pseudo random GUID. This algorithm is not suitable for
61
- * cryptographic purposes and should not be used therefore.
55
+ * cryptographic purposes and should not be used for such.
62
56
  *
63
- * @returns {string} - Generated GUID
57
+ * @returns {string} Generated GUID
64
58
  *
65
59
  * @static
66
60
  * @private
67
61
  */
68
62
  DataProviderBase._createGuid = function() {
69
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
70
- var r = Math.random() * 16 | 0, // Bitwise OR is equivalent to Math.floor() but faster
71
- 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
72
70
 
73
71
  return v.toString(16);
74
72
  });
@@ -78,18 +76,17 @@
78
76
  * The function returns array of columns that need special conversion for values.
79
77
  * E.g. handling data from association/navigationProperty
80
78
  *
81
- * @param {Array} aColumns - Configuration object
82
- * @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
83
81
  *
84
82
  * @static
85
83
  * @private
86
84
  */
87
85
  DataProviderBase.getColumnsToConvert = function(aColumns) {
88
86
  return aColumns.reduce(function(result, col) {
89
- var properties;
90
87
 
91
88
  // Handle aggregated properties and single properties
92
- properties = col.property instanceof Array ? col.property : [col.property];
89
+ const properties = col.property instanceof Array ? col.property : [col.property];
93
90
  // Handle unitProperty which too could be from an association
94
91
  if (col.unitProperty) {
95
92
  properties.push(col.unitProperty);
@@ -98,7 +95,7 @@
98
95
  properties.forEach(function(property) {
99
96
 
100
97
  // Convert navigation property and date fields
101
- var aKeys = property.split('/');
98
+ const aKeys = property.split("/");
102
99
 
103
100
  if (aKeys.length > 1) {
104
101
  result.push({
@@ -123,9 +120,7 @@
123
120
  * @public
124
121
  */
125
122
  DataProviderBase.getDataConverter = function(mSettings) {
126
- var aColumns, aColumnsToConvert;
127
-
128
- aColumns = mSettings.workbook.columns;
123
+ let aColumns = mSettings.workbook.columns;
129
124
 
130
125
  /* Add hierarachyLevel as virtual column for NavigationProperty conversion */
131
126
  if (mSettings.workbook.hierarchyLevel) {
@@ -134,7 +129,7 @@
134
129
  }]);
135
130
  }
136
131
 
137
- aColumnsToConvert = this.getColumnsToConvert(aColumns);
132
+ const aColumnsToConvert = this.getColumnsToConvert(aColumns);
138
133
 
139
134
  return function(aRows) {
140
135
  return DataProviderBase._convertData(aRows, aColumnsToConvert);
@@ -144,9 +139,9 @@
144
139
  /**
145
140
  * Function to process the JSON result array from a ODataService.
146
141
  *
147
- * @param {Array} aRows - Data array that contains the received data
148
- * @param {Array} aCols - Columns that need to be converted
149
- * @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
150
145
  *
151
146
  * @static
152
147
  * @private
@@ -165,10 +160,10 @@
165
160
  * Gets converted property value from raw data.
166
161
  * Navigation properties are parsed.
167
162
  *
168
- * @param {object} oRow - Raw data row
169
- * @param {object} oCol - Column information
170
- * @param {Array} oCol.keys - Property name or key path for navigation properties
171
- * @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
172
167
  *
173
168
  * @static
174
169
  * @private
@@ -176,7 +171,7 @@
176
171
  DataProviderBase._getValue = function(oRow, oCol) {
177
172
 
178
173
  // Get property value
179
- var value = oCol.keys.reduce(function(obj, key) {
174
+ const value = oCol.keys.reduce(function(obj, key) {
180
175
  return obj && obj[key];
181
176
  }, oRow);
182
177
 
@@ -187,14 +182,14 @@
187
182
  * The function requests several chunks of data until the maximum
188
183
  * amount of data is fetched.
189
184
  *
190
- * @param {function} fnProcessCallback - Callback function that is triggered when data is received
191
- * @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
192
187
  *
193
188
  * @since 1.77
194
189
  * @public
195
190
  */
196
191
  DataProviderBase.prototype.requestData = function(fnProcessCallback) {
197
- var mDataSource = this.mSettings.dataSource;
192
+ const mDataSource = this.mSettings.dataSource;
198
193
 
199
194
  this.fnProcessCallback = fnProcessCallback;
200
195
 
@@ -202,7 +197,7 @@
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,37 +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, bWasServerSidePaging;
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
- bWasServerSidePaging = this.mRequest?.dataUrl.includes("$skiptoken");
245
- 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;
246
239
 
247
240
  this.iAvailableRows += iFetchedRows;
248
241
 
249
- // Check if next url is provided
250
- sNextUrl = (oResult && oResult['@odata.nextLink'] || (oResult.d && oResult.d.__next)) || null;
251
-
252
- iRemainingRows = this.iTotalRows - this.iAvailableRows;
242
+ const iRemainingRows = this.iTotalRows - this.iAvailableRows;
243
+ const mCallbackParams = {};
253
244
 
254
245
  mCallbackParams.finished = this._isFinished(iFetchedRows, sNextUrl, iRemainingRows, bWasServerSidePaging);
255
246
  mCallbackParams.progress = this.iTotalRows;
256
247
  mCallbackParams.total = this.iTotalRows < this.iCount ? this.iTotalRows : this.iCount;
257
248
  mCallbackParams.fetched = this.iAvailableRows;
258
249
 
259
-
260
250
  if (!mCallbackParams.finished) {
261
251
  // Trigger next page request before processing received data. Fetch only configured/max limit rows
262
252
  this.mRequest.dataUrl = this._getUrl(this.iAvailableRows, Math.min(this.iBatchSize, iRemainingRows), sNextUrl);
@@ -273,8 +263,19 @@
273
263
  };
274
264
 
275
265
  /**
276
- * The function returns array of columns that need special conversion for values.
277
- * E.g. handling data from association/navigationProperty
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>.
278
279
  *
279
280
  * @param {number} iFetchedRows Number of rows fetched
280
281
  * @param {string} sNextUrl Next url to fetch data
@@ -285,23 +286,21 @@
285
286
  * @private
286
287
  */
287
288
  DataProviderBase.prototype._isFinished = function(iFetchedRows, sNextUrl, iRemainingRows, bWasServerSidePaging) {
288
- if (iFetchedRows === 0 || iRemainingRows <= 0) {
289
- return true;
290
- }
291
-
292
- if (sNextUrl) {
289
+ // Most common scenario - request gets fulfilled
290
+ if (iFetchedRows === this.iBatchSize) {
293
291
  return false;
294
292
  }
295
293
 
296
- if (iFetchedRows > this.iBatchSize) {
294
+ if (iFetchedRows === 0 || iRemainingRows <= 0) {
297
295
  return true;
298
296
  }
299
297
 
300
- if (iFetchedRows < this.iBatchSize && bWasServerSidePaging) {
298
+ if (sNextUrl) {
301
299
  return false;
302
300
  }
303
301
 
304
- if (iFetchedRows === this.iBatchSize) {
302
+ // If server side paging was used, the data chunks can be smaller than the batch size
303
+ if (iFetchedRows < this.iBatchSize && bWasServerSidePaging) {
305
304
  return false;
306
305
  }
307
306
 
@@ -311,7 +310,7 @@
311
310
  /**
312
311
  * Inner function that processes request handler exceptions.
313
312
  *
314
- * @param {string} sMessage - Error message.
313
+ * @param {string} sMessage Error message.
315
314
  *
316
315
  * @private
317
316
  */
@@ -324,22 +323,20 @@
324
323
  /**
325
324
  * Nested function to remove not used information from the URL
326
325
  *
327
- * @param {string} sUrl - A URL that may contain a path, hash and request parameters
328
- * @returns {string} - A clean URL
326
+ * @param {string} sUrl A URL that may contain a path, hash and request parameters
329
327
  *
328
+ * @returns {string} A clean URL
330
329
  * @private
331
330
  */
332
331
  DataProviderBase.prototype._cleanUrl = function(sUrl) {
333
- var oURL;
334
-
335
332
  if (!sUrl) {
336
- return '';
333
+ return "";
337
334
  }
338
335
 
339
- oURL = new URL(sUrl);
336
+ const oURL = new URL(sUrl);
340
337
 
341
- oURL.hash = oURL.search = '';
342
- oURL.pathname += oURL.pathname.endsWith('/') ? '' : '/';
338
+ oURL.hash = oURL.search = "";
339
+ oURL.pathname += oURL.pathname.endsWith("/") ? "" : "/";
343
340
 
344
341
  return oURL.toString();
345
342
  };
@@ -350,25 +347,26 @@
350
347
  * @private
351
348
  */
352
349
  DataProviderBase.prototype._prepareDataUrl = function() {
353
- var mDataSource = this.mSettings.dataSource;
354
- 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]+/;
355
352
 
356
353
  if (!mDataSource.dataUrl) {
357
- throw 'Unable to load data - no URL provided.';
354
+ throw "Unable to load data - no URL provided.";
358
355
  }
359
356
 
360
- mDataUrl = new URL(mDataSource.dataUrl);
361
- mDataUrl.search = mDataUrl.search || '';
357
+ const mDataUrl = new URL(mDataSource.dataUrl);
358
+
359
+ mDataUrl.search = mDataUrl.search || "";
362
360
 
363
361
  // Add missing $skip if needed
364
362
  if (!reSkip.test(mDataUrl.search)) {
365
363
  // Apply $skip with some numeric dummy value that matches the regexp in DataProviderBase#_getUrl
366
- mDataUrl.search += (mDataUrl.search.length ? '&$skip=' : '$skip=') + 0;
364
+ mDataUrl.search += (mDataUrl.search.length ? "&$skip=" : "$skip=") + 0;
367
365
  }
368
366
  // Add missing $top if needed
369
367
  if (!reTop.test(mDataUrl.search)) {
370
368
  // Apply $top with some numeric dummy value that matches the regexp in DataProviderBase#_getUrl
371
- mDataUrl.search += '&$top=' + 0;
369
+ mDataUrl.search += "&$top=" + 0;
372
370
  }
373
371
 
374
372
  this.mSettings.dataSource.dataUrl = mDataUrl.toString();
@@ -377,17 +375,15 @@
377
375
  /**
378
376
  * Creates the download URL for the next query.
379
377
  *
380
- * @param {number} iSkip - The amount of items that are already present and will be skipped
381
- * @param {number} iTop - The amount of items that should be requested with this query
382
- * @param {string} [sNextUrl] - A reference to the next bulk of data that was returned by the previous request
383
- * @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
384
381
  *
382
+ * @returns {string} The URL for the next query
385
383
  * @private
386
384
  */
387
385
  DataProviderBase.prototype._getUrl = function(iSkip, iTop, sNextUrl) {
388
- var oDataUrl, oNextUrl;
389
-
390
- oDataUrl = new URL(this.mSettings.dataSource.dataUrl);
386
+ const oDataUrl = new URL(this.mSettings.dataSource.dataUrl);
391
387
 
392
388
  /*
393
389
  * Use $skiptoken from response to query the next items.
@@ -398,185 +394,243 @@
398
394
  */
399
395
  if (sNextUrl) {
400
396
  // sNextUrl can be relative, therefore we need to apply a base even though it is not used
401
- oNextUrl = new URL(sNextUrl, oDataUrl.origin);
397
+ const oNextUrl = new URL(sNextUrl, oDataUrl.origin);
398
+
402
399
  oDataUrl.search = oNextUrl.search;
403
400
  } else { // Use $skip and $top
404
- oDataUrl.search = (oDataUrl.search || '')
405
- .replace(/\$skip\=[0-9]+/g, '$skip=' + iSkip)
406
- .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);
407
404
  }
408
405
 
409
406
  return oDataUrl.toString();
410
407
  };
411
408
 
412
409
  /**
413
- * This method creates an XMLHttpRequest from the provided
414
- * configuration and requests the data from the backend. The
415
- * configuration is configured to use OData services.
416
- *
417
- * @param {object} oRequest - Request configuration object
418
- * @param {string} oRequest.method - References the HTTP method that is used (default: GET)
419
- * @param {string} oRequest.dataUrl - References the resource URL that gets invoked
420
- * @param {string} oRequest.serviceUrl - References the service URL that gets invoked
421
- * @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.
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
422
420
  *
421
+ * @return {Promise} Returns a Promise that will be resolve once the requested data was fetched
422
+ * @async
423
423
  * @private
424
424
  */
425
- DataProviderBase.prototype.sendRequest = function(oRequest) {
426
- 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
+ }
427
429
 
428
- if (typeof oRequest !== 'object' || oRequest === null || typeof oRequest.dataUrl !== 'string') {
429
- 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
+ }
430
447
  }
431
448
 
432
- fnSendRequest = (oRequest.method === 'BATCH' && oRequest.serviceUrl ? this.sendBatchRequest : this.sendGetRequest).bind(this);
449
+ oResponse = bBatchRequest ? await DataProviderBase.getBatchResponse(oResponse) : oResponse;
433
450
 
434
- 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;
435
457
  };
436
458
 
437
459
  /**
438
- * Creates a $batch request and sends it to the backend service.
460
+ * Creates a <code>Request</code> object for an OData GET request.
439
461
  *
440
- * @param {object} oRequest - Request object that contains all necessary information to create the batch request
441
- * @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
442
465
  *
466
+ * @returns {Request} The request object
467
+ * @static
443
468
  * @private
444
469
  */
445
- DataProviderBase.prototype.sendBatchRequest = function(oRequest) {
446
- return new Promise(function(fnResolve, fnReject) {
447
- var xhr = new XMLHttpRequest();
448
- var boundary = 'batch_' + DataProviderBase._createGuid();
449
- var getUrl = oRequest.dataUrl.split(oRequest.serviceUrl)[1];
450
- var body = [];
451
- var sKey, sValue;
452
-
453
- xhr.onload = function() {
454
- var responseText, aLines, iEnd, iLength, iStart, sHttpStatus, aMatch;
455
-
456
- aLines = this.responseText.split('\r\n');
457
-
458
- // TBD: check return codes
459
- iStart = 0;
460
- iLength = aLines.length;
461
- iEnd = iLength - 1;
462
-
463
- aLines.forEach(function(sLine) {
464
- aMatch = sLine.match(/^HTTP\/1\.[0|1] ([1-9][0-9]{2})/);
465
-
466
- if (Array.isArray(aMatch) && aMatch[1]) {
467
- sHttpStatus = aMatch[1];
468
- }
469
- });
470
-
471
- while (iStart < iLength && aLines[iStart].slice(0, 1) !== '{') {
472
- iStart++;
473
- }
470
+ DataProviderBase.createGetRequest = function(mSettings, oAbortController) {
471
+ const oHeaders = new Headers(mSettings.headers);
474
472
 
475
- while (iEnd > 0 && aLines[iEnd].slice(-1) !== '}') {
476
- iEnd--;
477
- }
478
- aLines = aLines.slice(iStart, iEnd + 1);
479
- responseText = aLines.join('\r\n');
473
+ oHeaders.set("Accept", "application/json");
480
474
 
481
- if (sHttpStatus && parseInt(sHttpStatus) >= 400) {
482
- // Provide a fallback in case the responseText is empty
483
- 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
+ };
484
481
 
485
- return;
486
- }
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
+ };
487
511
 
488
- try {
489
- fnResolve(JSON.parse(responseText));
490
- } catch (e) {
491
- fnReject(DataProviderBase.HTTP_WRONG_RESPONSE_MSG + responseText);
492
- }
493
- };
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 = [];
494
525
 
495
- /* Handle technical request errors like timeout, disconnect, etc. */
496
- xhr.onerror = function() {
497
- fnReject(this.responseText || DataProviderBase.HTTP_ERROR_MSG);
498
- };
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`);
499
531
 
500
- // Create request
501
- xhr.open('POST', oRequest.serviceUrl + '$batch', true);
532
+ /* Set header information */
533
+ aBody.push("Accept:application/json");
502
534
 
503
- xhr.setRequestHeader('Accept', 'multipart/mixed');
504
- xhr.setRequestHeader('Content-Type', 'multipart/mixed;boundary=' + boundary);
535
+ for (const [sKey, sValue] of oHeaders.entries()) {
505
536
 
506
- body.push('--' + boundary);
507
- body.push('Content-Type: application/http');
508
- body.push('Content-Transfer-Encoding: binary');
509
- body.push('');
510
- body.push('GET ' + getUrl + ' HTTP/1.1');
537
+ if (sKey != "accept") {
538
+ aBody.push(sKey + ":" + sValue);
539
+ }
540
+ }
511
541
 
512
- /* Set header information on the request as well as on the batch request */
513
- body.push('Accept:application/json');
542
+ aBody.push("");
543
+ aBody.push("");
544
+ aBody.push("--" + sBoundary + "--");
545
+ aBody.push("");
514
546
 
515
- for (sKey in oRequest.headers) {
516
- sValue = oRequest.headers[sKey];
547
+ return aBody.join("\r\n");
548
+ };
517
549
 
518
- if (sKey.toLowerCase() != 'accept') {
519
- xhr.setRequestHeader(sKey, sValue);
520
- body.push(sKey + ':' + sValue);
521
- }
522
- }
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)));
523
572
 
524
- body.push('');
525
- body.push('');
526
- body.push('--' + boundary + '--');
527
- body.push('');
528
- body = body.join('\r\n');
529
- 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("}"));
530
575
 
531
- this.oPendingXHR = xhr;
532
- }.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
+ });
533
592
  };
534
593
 
535
594
  /**
536
- * 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.
537
602
  *
538
- * @param {object} oRequest - Request object that contains all necessary information to create the batch request
539
- * @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
540
604
  *
605
+ * @returns {Array} The response data as Array
606
+ * @static
607
+ * @async
541
608
  * @private
542
609
  */
543
- DataProviderBase.prototype.sendGetRequest = function(oRequest) {
544
- return new Promise(function(fnResolve, fnReject) {
545
- var sHeaderKey;
546
- var xhr = new XMLHttpRequest();
610
+ DataProviderBase.getHarmonizedBodyFrom = async function(oResponse) {
611
+ const oResult = {
612
+ data: []
613
+ };
547
614
 
548
- xhr.onload = function() {
549
- if (this.status >= 400) {
550
- // Provide a fallback in case the responseText is empty
551
- fnReject(this.responseText || this.statusText || DataProviderBase.HTTP_ERROR_MSG);
615
+ if (!oResponse) {
616
+ return oResult;
617
+ }
552
618
 
553
- return;
554
- }
555
- try {
556
- fnResolve(JSON.parse(this.responseText));
557
- } catch (e) {
558
- fnReject(DataProviderBase.HTTP_WRONG_RESPONSE_MSG + this.responseText);
559
- }
560
- };
619
+ const oBody = await oResponse.json();
561
620
 
562
- /* Handle technical request errors like timeout, disconnect, etc. */
563
- xhr.onerror = function() {
564
- fnReject(this.responseText || DataProviderBase.HTTP_ERROR_MSG);
565
- };
621
+ if (!oBody) {
622
+ return oResult;
623
+ }
566
624
 
567
- xhr.open('GET', oRequest.dataUrl, true);
568
- xhr.setRequestHeader('accept', 'application/json');
625
+ const aData = oBody.value || oBody.d?.results || oBody.d || oBody;
569
626
 
570
- /* Set custom header information on the request */
571
- for (sHeaderKey in oRequest.headers) {
572
- if (sHeaderKey.toLowerCase() !== 'accept') {
573
- xhr.setRequestHeader(sHeaderKey, oRequest.headers[sHeaderKey]);
574
- }
575
- }
627
+ oResult.nextUrl = oBody["@odata.nextLink"] ?? oBody.d?.__next;
628
+
629
+ if (Array.isArray(aData)) {
630
+ oResult.data = aData;
631
+ }
576
632
 
577
- xhr.send();
578
- this.oPendingXHR = xhr;
579
- }.bind(this));
633
+ return oResult;
580
634
  };
581
635
 
582
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.1
23
+ * @version 1.131.0
24
24
  *
25
25
  * @since 1.110
26
26
  * @alias sap.ui.export.util.Filter