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