@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 +1 -1
- package/src/sap/ui/export/.library +1 -1
- package/src/sap/ui/export/ExportBase.js +21 -21
- package/src/sap/ui/export/ExportHandler.js +1 -1
- package/src/sap/ui/export/ExportUtils.js +2 -2
- package/src/sap/ui/export/PortableDocument.js +1 -1
- package/src/sap/ui/export/Spreadsheet.js +31 -36
- package/src/sap/ui/export/SpreadsheetExport.js +1 -1
- package/src/sap/ui/export/library.js +2 -2
- package/src/sap/ui/export/provider/DataProviderBase.js +278 -224
- package/src/sap/ui/export/util/Filter.js +1 -1
package/package.json
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
sap.ui.define([
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
-
[
|
|
52
|
-
if (typeof mSettings[sProperty] !==
|
|
53
|
-
this._mSettings[sProperty] = 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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
203
|
+
const mParameters = this._mSettings;
|
|
204
204
|
|
|
205
205
|
if (this.bIsDestroyed) {
|
|
206
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
172
|
+
* const mSettings = {
|
|
173
173
|
* workbook: {
|
|
174
174
|
* columns: aColumns,
|
|
175
175
|
* context: {
|
|
176
176
|
* application: 'Debug Test Application',
|
|
177
|
-
* version: '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
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
607
|
-
|
|
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
|
-
|
|
647
|
+
const that = this;
|
|
653
648
|
|
|
654
649
|
return new Promise(function(fnResolve, fnReject) {
|
|
655
650
|
|
|
656
|
-
|
|
657
|
-
|
|
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
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
36
|
+
version: "1.131.0"
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
(function(fClass) {
|
|
7
|
-
|
|
7
|
+
"use strict";
|
|
8
8
|
|
|
9
|
-
if (typeof globalThis.sap?.ui?.define ===
|
|
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
|
-
|
|
15
|
+
"use strict";
|
|
16
16
|
|
|
17
17
|
// eslint-disable-next-line
|
|
18
|
-
/* global URL
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
57
|
-
DataProviderBase.HTTP_WRONG_RESPONSE_MSG =
|
|
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
|
|
55
|
+
* cryptographic purposes and should not be used for such.
|
|
62
56
|
*
|
|
63
|
-
* @returns {string}
|
|
57
|
+
* @returns {string} Generated GUID
|
|
64
58
|
*
|
|
65
59
|
* @static
|
|
66
60
|
* @private
|
|
67
61
|
*/
|
|
68
62
|
DataProviderBase._createGuid = function() {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
82
|
-
* @returns {Array}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
148
|
-
* @param {Array} aCols
|
|
149
|
-
* @returns {Array}
|
|
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
|
|
169
|
-
* @param {object} oCol
|
|
170
|
-
* @param {Array} oCol.keys
|
|
171
|
-
* @returns {number|string|boolean}
|
|
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
|
-
|
|
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
|
|
191
|
-
* @returns {object}
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
218
|
-
this.
|
|
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 {
|
|
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(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
this.oPendingXHR = null;
|
|
229
|
+
DataProviderBase.prototype.fnOnDataReceived = async function(oResponse) {
|
|
230
|
+
this._oPendingRequest = null;
|
|
231
|
+
|
|
237
232
|
if (this.bCanceled) {
|
|
238
|
-
return; //
|
|
233
|
+
return; // Cancelled by the application
|
|
239
234
|
}
|
|
240
235
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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
|
|
277
|
-
*
|
|
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
|
-
|
|
289
|
-
|
|
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
|
|
294
|
+
if (iFetchedRows === 0 || iRemainingRows <= 0) {
|
|
297
295
|
return true;
|
|
298
296
|
}
|
|
299
297
|
|
|
300
|
-
if (
|
|
298
|
+
if (sNextUrl) {
|
|
301
299
|
return false;
|
|
302
300
|
}
|
|
303
301
|
|
|
304
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
354
|
-
|
|
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
|
|
354
|
+
throw "Unable to load data - no URL provided.";
|
|
358
355
|
}
|
|
359
356
|
|
|
360
|
-
mDataUrl = new URL(mDataSource.dataUrl);
|
|
361
|
-
|
|
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 ?
|
|
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 +=
|
|
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
|
|
381
|
-
* @param {number} iTop
|
|
382
|
-
* @param {string} [sNextUrl]
|
|
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
|
-
|
|
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,
|
|
406
|
-
.replace(/\$top\=[0-9]+/g,
|
|
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
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
* @param {
|
|
420
|
-
* @param {string}
|
|
421
|
-
* @
|
|
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(
|
|
426
|
-
|
|
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
|
-
|
|
429
|
-
|
|
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
|
-
|
|
449
|
+
oResponse = bBatchRequest ? await DataProviderBase.getBatchResponse(oResponse) : oResponse;
|
|
433
450
|
|
|
434
|
-
|
|
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
|
|
460
|
+
* Creates a <code>Request</code> object for an OData GET request.
|
|
439
461
|
*
|
|
440
|
-
* @param {object}
|
|
441
|
-
* @
|
|
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.
|
|
446
|
-
|
|
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
|
-
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
475
|
+
return new Request(mSettings.dataUrl, {
|
|
476
|
+
cache: "no-store",
|
|
477
|
+
headers: oHeaders,
|
|
478
|
+
signal: oAbortController.signal
|
|
479
|
+
});
|
|
480
|
+
};
|
|
484
481
|
|
|
485
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
501
|
-
|
|
532
|
+
/* Set header information */
|
|
533
|
+
aBody.push("Accept:application/json");
|
|
502
534
|
|
|
503
|
-
|
|
504
|
-
xhr.setRequestHeader('Content-Type', 'multipart/mixed;boundary=' + boundary);
|
|
535
|
+
for (const [sKey, sValue] of oHeaders.entries()) {
|
|
505
536
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
body.push('GET ' + getUrl + ' HTTP/1.1');
|
|
537
|
+
if (sKey != "accept") {
|
|
538
|
+
aBody.push(sKey + ":" + sValue);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
511
541
|
|
|
512
|
-
|
|
513
|
-
|
|
542
|
+
aBody.push("");
|
|
543
|
+
aBody.push("");
|
|
544
|
+
aBody.push("--" + sBoundary + "--");
|
|
545
|
+
aBody.push("");
|
|
514
546
|
|
|
515
|
-
|
|
516
|
-
|
|
547
|
+
return aBody.join("\r\n");
|
|
548
|
+
};
|
|
517
549
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
525
|
-
|
|
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
|
-
|
|
532
|
-
|
|
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
|
-
*
|
|
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 {
|
|
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.
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
610
|
+
DataProviderBase.getHarmonizedBodyFrom = async function(oResponse) {
|
|
611
|
+
const oResult = {
|
|
612
|
+
data: []
|
|
613
|
+
};
|
|
547
614
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
fnReject(this.responseText || this.statusText || DataProviderBase.HTTP_ERROR_MSG);
|
|
615
|
+
if (!oResponse) {
|
|
616
|
+
return oResult;
|
|
617
|
+
}
|
|
552
618
|
|
|
553
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
};
|
|
621
|
+
if (!oBody) {
|
|
622
|
+
return oResult;
|
|
623
|
+
}
|
|
566
624
|
|
|
567
|
-
|
|
568
|
-
xhr.setRequestHeader('accept', 'application/json');
|
|
625
|
+
const aData = oBody.value || oBody.d?.results || oBody.d || oBody;
|
|
569
626
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
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.
|
|
23
|
+
* @version 1.131.0
|
|
24
24
|
*
|
|
25
25
|
* @since 1.110
|
|
26
26
|
* @alias sap.ui.export.util.Filter
|