@sap-ux/preview-middleware 1.1.3 → 1.2.1
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/README.md +31 -29
- package/dist/base/config.d.ts +55 -2
- package/dist/base/config.js +158 -26
- package/dist/base/flp.d.ts +24 -0
- package/dist/base/flp.js +116 -18
- package/dist/client/adp/api-handler.js +5 -1
- package/dist/client/adp/api-handler.ts +11 -4
- package/dist/client/adp/controllers/ControllerExtension.controller.js +95 -35
- package/dist/client/adp/controllers/ControllerExtension.controller.ts +121 -42
- package/dist/client/adp/extend-controller.ts +1 -0
- package/dist/client/adp/quick-actions/common/add-controller-to-page.js +2 -2
- package/dist/client/adp/quick-actions/common/add-controller-to-page.ts +2 -2
- package/dist/client/adp/quick-actions/fe-v4/create-page-action.js +3 -2
- package/dist/client/adp/quick-actions/fe-v4/create-page-action.ts +3 -2
- package/dist/client/adp/quick-actions/fe-v4/create-table-action-config-change.js +3 -2
- package/dist/client/adp/quick-actions/fe-v4/create-table-action-config-change.ts +3 -2
- package/dist/client/adp/ui/ControllerExtension.fragment.xml +33 -6
- package/dist/client/adp/utils.js +73 -5
- package/dist/client/adp/utils.ts +84 -5
- package/dist/client/flp/WorkspaceConnector.js +7 -5
- package/dist/client/flp/WorkspaceConnector.ts +7 -5
- package/dist/client/flp/common.js +293 -1
- package/dist/client/flp/common.ts +344 -1
- package/dist/client/flp/sandbox1Init.js +187 -0
- package/dist/client/flp/sandbox1Init.ts +174 -0
- package/dist/client/flp/sandbox2AfterInit.js +66 -0
- package/dist/client/flp/sandbox2AfterInit.ts +63 -0
- package/dist/client/flp/sandbox2BeforeInit.js +67 -0
- package/dist/client/flp/sandbox2BeforeInit.ts +60 -0
- package/dist/client/messagebundle.properties +3 -0
- package/dist/types/index.d.ts +21 -0
- package/package.json +3 -3
- package/templates/flp/cdm.ejs +1 -1
- package/templates/flp/sandbox.ejs +1 -1
- package/templates/flp/sandbox2.ejs +12 -44
- package/dist/client/flp/init.js +0 -465
- package/dist/client/flp/init.ts +0 -498
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import Button from 'sap/m/Button';
|
|
3
3
|
import type Dialog from 'sap/m/Dialog';
|
|
4
4
|
import Input from 'sap/m/Input';
|
|
5
|
+
import type RadioButtonGroup from 'sap/m/RadioButtonGroup';
|
|
5
6
|
|
|
6
7
|
/** sap.ui.core */
|
|
7
8
|
import type UI5Element from 'sap/ui/core/Element';
|
|
@@ -25,16 +26,16 @@ import { getResourceModel, getTextBundle, TextBundle } from '../../i18n.js';
|
|
|
25
26
|
import { getControlById } from '../../utils/core.js';
|
|
26
27
|
import { getError } from '../../utils/error.js';
|
|
27
28
|
import { sendInfoCenterMessage } from '../../utils/info-center-message.js';
|
|
28
|
-
import { getUi5Version, isLowerThanMinimalUi5Version } from '../../utils/version.js';
|
|
29
|
+
import { getUi5Version, isLowerThanMinimalUi5Version, type Ui5VersionInfo } from '../../utils/version.js';
|
|
29
30
|
import type { CodeExtResponse, ControllersResponse } from '../api-handler.js';
|
|
30
31
|
import { getExistingController, readControllers, writeChange, writeController } from '../api-handler.js';
|
|
31
32
|
import CommandExecutor from '../command-executor.js';
|
|
32
33
|
import type { DeferredExtendControllerData, ExtendControllerData } from '../extend-controller.js';
|
|
33
|
-
import { checkForExistingChange, getControllerInfo } from '../utils.js';
|
|
34
|
+
import { checkForExistingChange, getControllerInfo, getPendingCodeExtViewIds } from '../utils.js';
|
|
34
35
|
import BaseDialog from './BaseDialog.controller.js';
|
|
35
36
|
|
|
36
37
|
interface ControllerExtensionService {
|
|
37
|
-
add: (codeRef: string, viewId: string) => Promise<{ creation: string }>;
|
|
38
|
+
add: (codeRef: string, viewId: string, includeViewId?: boolean) => Promise<{ creation: string }>;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
type ControllerList = {
|
|
@@ -49,8 +50,14 @@ type ControllerModel = JSONModel & {
|
|
|
49
50
|
getProperty(sPath: '/controllerExists'): boolean;
|
|
50
51
|
getProperty(sPath: '/newControllerName'): string;
|
|
51
52
|
getProperty(sPath: '/viewId'): string;
|
|
52
|
-
getProperty(sPath: '/
|
|
53
|
+
getProperty(sPath: '/baseControllerPath'): string;
|
|
54
|
+
getProperty(sPath: '/instanceControllerPath'): string;
|
|
53
55
|
getProperty(sPath: '/controllerExtension'): string;
|
|
56
|
+
getProperty(sPath: '/isInstanceSpecific'): boolean;
|
|
57
|
+
getProperty(sPath: '/instanceSpecificVisibility'): boolean;
|
|
58
|
+
getProperty(sPath: '/baseControllerEnabled'): boolean;
|
|
59
|
+
getProperty(sPath: '/instanceControllerEnabled'): boolean;
|
|
60
|
+
getProperty(sPath: '/controllerTypeSelectedIndex'): number;
|
|
54
61
|
};
|
|
55
62
|
|
|
56
63
|
/**
|
|
@@ -59,8 +66,11 @@ type ControllerModel = JSONModel & {
|
|
|
59
66
|
export default class ControllerExtension extends BaseDialog<ControllerModel> {
|
|
60
67
|
/* The minimum version of UI5 framework which supports controller extensions. */
|
|
61
68
|
private static readonly CONTROLLER_EXT_MIN_UI5_VERSION = { major: 1, minor: 135 };
|
|
69
|
+
/* The minimum version of UI5 framework which supports instance-specific controller extensions. */
|
|
70
|
+
private static readonly INSTANCE_SPECIFIC_MIN_UI5_VERSION = { major: 1, minor: 143 };
|
|
62
71
|
public readonly data?: ExtendControllerData;
|
|
63
72
|
private bundle: TextBundle;
|
|
73
|
+
private ui5Version: Ui5VersionInfo;
|
|
64
74
|
|
|
65
75
|
constructor(
|
|
66
76
|
name: string,
|
|
@@ -88,6 +98,7 @@ export default class ControllerExtension extends BaseDialog<ControllerModel> {
|
|
|
88
98
|
|
|
89
99
|
const resourceModel = await getResourceModel('open.ux.preview.client');
|
|
90
100
|
this.bundle = await getTextBundle();
|
|
101
|
+
this.ui5Version = await getUi5Version();
|
|
91
102
|
|
|
92
103
|
await this.buildDialogData();
|
|
93
104
|
|
|
@@ -181,10 +192,12 @@ export default class ControllerExtension extends BaseDialog<ControllerModel> {
|
|
|
181
192
|
|
|
182
193
|
const controllerName = this.model.getProperty('/newControllerName');
|
|
183
194
|
const viewId = this.model.getProperty('/viewId');
|
|
195
|
+
const isInstanceSpecific = this.model.getProperty('/isInstanceSpecific');
|
|
184
196
|
|
|
185
|
-
const controllerRef = {
|
|
197
|
+
const controllerRef: DeferredExtendControllerData = {
|
|
186
198
|
codeRef: `coding/${controllerName}.js`,
|
|
187
|
-
viewId
|
|
199
|
+
viewId,
|
|
200
|
+
instanceSpecific: isInstanceSpecific
|
|
188
201
|
};
|
|
189
202
|
|
|
190
203
|
if (this.data) {
|
|
@@ -193,16 +206,13 @@ export default class ControllerExtension extends BaseDialog<ControllerModel> {
|
|
|
193
206
|
await this.createNewController(controllerName, controllerRef);
|
|
194
207
|
}
|
|
195
208
|
|
|
196
|
-
if (this.data &&
|
|
209
|
+
if (this.data && this.isControllerExtensionSupported()) {
|
|
197
210
|
await sendInfoCenterMessage({
|
|
198
211
|
title: { key: 'ADP_CREATE_CONTROLLER_EXTENSION_TITLE' },
|
|
199
212
|
description: { key: 'ADP_CREATE_CONTROLLER_EXTENSION', params: [controllerName] },
|
|
200
213
|
type: MessageBarType.info
|
|
201
214
|
});
|
|
202
215
|
}
|
|
203
|
-
} else {
|
|
204
|
-
const controllerPath = this.model.getProperty('/controllerPath');
|
|
205
|
-
window.open(`vscode://file${controllerPath}`);
|
|
206
216
|
}
|
|
207
217
|
|
|
208
218
|
this.handleDialogClose();
|
|
@@ -216,46 +226,68 @@ export default class ControllerExtension extends BaseDialog<ControllerModel> {
|
|
|
216
226
|
const overlayControl = sap.ui.getCore().byId(selectorId) as unknown as ElementOverlay;
|
|
217
227
|
|
|
218
228
|
const { controllerName, viewId } = getControllerInfo(overlayControl);
|
|
219
|
-
const data = await this.getExistingController(controllerName);
|
|
229
|
+
const data = await this.getExistingController(controllerName, viewId);
|
|
220
230
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
'selector.controllerName',
|
|
225
|
-
controllerName
|
|
226
|
-
);
|
|
231
|
+
if (!data) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
227
234
|
|
|
228
|
-
|
|
229
|
-
|
|
235
|
+
// Combine persisted (server) and pending (command stack) changes to determine whether a base
|
|
236
|
+
// page controller and/or an instance-specific controller for this view already exist.
|
|
237
|
+
const pendingViewIds = getPendingCodeExtViewIds(this.rta, controllerName);
|
|
238
|
+
const baseExists = data.baseControllerExists || pendingViewIds.some((id) => !id);
|
|
239
|
+
const instanceExists = data.instanceControllerExists || pendingViewIds.includes(viewId);
|
|
240
|
+
|
|
241
|
+
const showInstanceSpecificOption = this.isInstanceSpecificSupported();
|
|
242
|
+
|
|
243
|
+
if (!showInstanceSpecificOption) {
|
|
244
|
+
if (pendingViewIds.length > 0) {
|
|
230
245
|
this.updateModelForExistingPendingChange();
|
|
231
|
-
} else if (data
|
|
232
|
-
this.updateModelForExistingController(data);
|
|
246
|
+
} else if (data.baseControllerExists) {
|
|
247
|
+
this.updateModelForExistingController(data, true);
|
|
233
248
|
} else {
|
|
234
|
-
this.updateModelForNewController(viewId, data.isTsSupported);
|
|
235
|
-
|
|
249
|
+
this.updateModelForNewController(viewId, data.isTsSupported, false, false, false);
|
|
236
250
|
await this.getControllers();
|
|
237
251
|
}
|
|
252
|
+
return;
|
|
238
253
|
}
|
|
254
|
+
|
|
255
|
+
if (baseExists && instanceExists) {
|
|
256
|
+
if (data.baseControllerExists || data.instanceControllerExists) {
|
|
257
|
+
this.updateModelForExistingController(data);
|
|
258
|
+
} else {
|
|
259
|
+
this.updateModelForExistingPendingChange();
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this.updateModelForNewController(viewId, data.isTsSupported, true, baseExists, instanceExists);
|
|
265
|
+
await this.getControllers();
|
|
239
266
|
}
|
|
240
267
|
/**
|
|
241
|
-
* Updates the model properties for
|
|
268
|
+
* Updates the model properties for existing controller(s).
|
|
269
|
+
* Shows all persisted controllers (base and/or instance) in the existing-controller form, each with its own link to open in VS Code.
|
|
242
270
|
*
|
|
243
|
-
* @param
|
|
271
|
+
* @param data - Server response containing existence flags and file paths.
|
|
272
|
+
* @param showVsCodeButton - When true (pre-1.143 single-controller path), shows an "Open in VS Code" begin-button instead of relying on the inline fragment links.
|
|
244
273
|
*/
|
|
245
|
-
private updateModelForExistingController(data: CodeExtResponse): void {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
this.model.setProperty('/
|
|
249
|
-
this.model.setProperty('/
|
|
250
|
-
this.model.setProperty('/
|
|
274
|
+
private updateModelForExistingController(data: CodeExtResponse, showVsCodeButton = false): void {
|
|
275
|
+
this.model.setProperty('/controllerExists', true);
|
|
276
|
+
this.model.setProperty('/baseControllerExists', data.baseControllerExists);
|
|
277
|
+
this.model.setProperty('/baseControllerPath', data.baseControllerPath);
|
|
278
|
+
this.model.setProperty('/baseControllerPathFromRoot', data.baseControllerPathFromRoot);
|
|
279
|
+
this.model.setProperty('/instanceControllerExists', data.instanceControllerExists);
|
|
280
|
+
this.model.setProperty('/instanceControllerPath', data.instanceControllerPath);
|
|
281
|
+
this.model.setProperty('/instanceControllerPathFromRoot', data.instanceControllerPathFromRoot);
|
|
282
|
+
this.model.setProperty('/isRunningInBAS', data.isRunningInBAS);
|
|
251
283
|
this.model.setProperty('/inputFormVisibility', false);
|
|
252
284
|
this.model.setProperty('/pendingChangeFormVisibility', false);
|
|
253
285
|
this.model.setProperty('/existingControllerFormVisibility', true);
|
|
254
286
|
|
|
255
|
-
if (isRunningInBAS) {
|
|
256
|
-
this.dialog.getBeginButton().setVisible(false);
|
|
257
|
-
} else {
|
|
287
|
+
if (showVsCodeButton && !data.isRunningInBAS) {
|
|
258
288
|
this.dialog.getBeginButton().setText('Open in VS Code').setEnabled(true);
|
|
289
|
+
} else {
|
|
290
|
+
this.dialog.getBeginButton().setVisible(false);
|
|
259
291
|
}
|
|
260
292
|
this.dialog.getEndButton().setText('Close');
|
|
261
293
|
}
|
|
@@ -277,25 +309,41 @@ export default class ControllerExtension extends BaseDialog<ControllerModel> {
|
|
|
277
309
|
*
|
|
278
310
|
* @param {string} viewId - The view ID.
|
|
279
311
|
* @param {boolean} isTsSupported - Whether TypeScript supported for the current project.
|
|
312
|
+
* @param {boolean} showInstanceSpecificOption - Whether to show the instance-specific radio button option.
|
|
313
|
+
* @param {boolean} baseExists - Whether a base page controller extension already exists.
|
|
314
|
+
* @param {boolean} instanceExists - Whether an instance-specific extension already exists for this view.
|
|
280
315
|
*/
|
|
281
|
-
private updateModelForNewController(
|
|
316
|
+
private updateModelForNewController(
|
|
317
|
+
viewId: string,
|
|
318
|
+
isTsSupported: boolean,
|
|
319
|
+
showInstanceSpecificOption: boolean,
|
|
320
|
+
baseExists: boolean,
|
|
321
|
+
instanceExists: boolean
|
|
322
|
+
): void {
|
|
282
323
|
this.model.setProperty('/viewId', viewId);
|
|
283
324
|
this.model.setProperty('/controllerExtension', isTsSupported ? '.ts' : '.js');
|
|
284
325
|
this.model.setProperty('/existingControllerFormVisibility', false);
|
|
285
326
|
this.model.setProperty('/pendingChangeFormVisibility', false);
|
|
286
327
|
this.model.setProperty('/inputFormVisibility', true);
|
|
328
|
+
this.model.setProperty('/instanceSpecificVisibility', showInstanceSpecificOption);
|
|
329
|
+
this.model.setProperty('/baseControllerEnabled', !baseExists);
|
|
330
|
+
this.model.setProperty('/instanceControllerEnabled', !instanceExists);
|
|
331
|
+
const selectedIndex = baseExists ? 1 : 0;
|
|
332
|
+
this.model.setProperty('/controllerTypeSelectedIndex', selectedIndex);
|
|
333
|
+
this.model.setProperty('/isInstanceSpecific', selectedIndex === 1);
|
|
287
334
|
}
|
|
288
335
|
|
|
289
336
|
/**
|
|
290
337
|
* Retrieves existing controller data if found in the project's workspace.
|
|
291
338
|
*
|
|
292
339
|
* @param controllerName Controller name that exists in the view.
|
|
340
|
+
* @param viewId ID of the current view, used to detect an existing instance-specific extension.
|
|
293
341
|
* @returns Returns existing controller data.
|
|
294
342
|
*/
|
|
295
|
-
private async getExistingController(controllerName: string): Promise<CodeExtResponse | undefined> {
|
|
343
|
+
private async getExistingController(controllerName: string, viewId: string): Promise<CodeExtResponse | undefined> {
|
|
296
344
|
let data: CodeExtResponse | undefined;
|
|
297
345
|
try {
|
|
298
|
-
data = await getExistingController(controllerName);
|
|
346
|
+
data = await getExistingController(controllerName, viewId);
|
|
299
347
|
} catch (e) {
|
|
300
348
|
const error = getError(e);
|
|
301
349
|
await sendInfoCenterMessage({
|
|
@@ -337,7 +385,7 @@ export default class ControllerExtension extends BaseDialog<ControllerModel> {
|
|
|
337
385
|
controllerName: string,
|
|
338
386
|
controllerRef: DeferredExtendControllerData
|
|
339
387
|
): Promise<void> {
|
|
340
|
-
if (
|
|
388
|
+
if (this.isControllerExtensionSupported()) {
|
|
341
389
|
await this.createControllerCommand(controllerName, controllerRef);
|
|
342
390
|
return;
|
|
343
391
|
}
|
|
@@ -346,7 +394,11 @@ export default class ControllerExtension extends BaseDialog<ControllerModel> {
|
|
|
346
394
|
|
|
347
395
|
const service = await this.rta.getService<ControllerExtensionService>('controllerExtension');
|
|
348
396
|
|
|
349
|
-
const change = await service.add(
|
|
397
|
+
const change = await service.add(
|
|
398
|
+
controllerRef.codeRef,
|
|
399
|
+
controllerRef.viewId,
|
|
400
|
+
controllerRef.instanceSpecific
|
|
401
|
+
);
|
|
350
402
|
change.creation = new Date().toISOString();
|
|
351
403
|
|
|
352
404
|
await writeChange(change);
|
|
@@ -401,8 +453,35 @@ export default class ControllerExtension extends BaseDialog<ControllerModel> {
|
|
|
401
453
|
});
|
|
402
454
|
}
|
|
403
455
|
|
|
404
|
-
private
|
|
405
|
-
|
|
406
|
-
|
|
456
|
+
private isControllerExtensionSupported(): boolean {
|
|
457
|
+
return !isLowerThanMinimalUi5Version(this.ui5Version, ControllerExtension.CONTROLLER_EXT_MIN_UI5_VERSION);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Handles the selection change on the controller type for the radio button group.
|
|
462
|
+
*
|
|
463
|
+
* @param event Event
|
|
464
|
+
*/
|
|
465
|
+
onControllerTypeSelectionChange(event: Event): void {
|
|
466
|
+
const group = event.getSource<RadioButtonGroup>();
|
|
467
|
+
this.model.setProperty('/isInstanceSpecific', group.getSelectedIndex() === 1);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Opens the base page controller extension file in VS Code.
|
|
472
|
+
*/
|
|
473
|
+
onOpenBaseController(): void {
|
|
474
|
+
window.open(`vscode://file${this.model.getProperty('/baseControllerPath')}`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Opens the instance-specific controller extension file in VS Code.
|
|
479
|
+
*/
|
|
480
|
+
onOpenInstanceController(): void {
|
|
481
|
+
window.open(`vscode://file${this.model.getProperty('/instanceControllerPath')}`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private isInstanceSpecificSupported(): boolean {
|
|
485
|
+
return !isLowerThanMinimalUi5Version(this.ui5Version, ControllerExtension.INSTANCE_SPECIFIC_MIN_UI5_VERSION);
|
|
407
486
|
}
|
|
408
487
|
}
|
|
@@ -44,8 +44,8 @@ sap.ui.define(["sap/ui/dt/OverlayRegistry", "../../../utils/version", "../../uti
|
|
|
44
44
|
const control = getRelevantControlFromActivePage(this.context.controlIndex, this.context.view, CONTROL_TYPES)[0];
|
|
45
45
|
if (control) {
|
|
46
46
|
const controlInfo = getControllerInfoForControl(control);
|
|
47
|
-
const data = await getExistingController(controlInfo.controllerName);
|
|
48
|
-
this.controllerExists = data?.
|
|
47
|
+
const data = await getExistingController(controlInfo.controllerName, controlInfo.viewId);
|
|
48
|
+
this.controllerExists = data?.baseControllerExists || data?.instanceControllerExists;
|
|
49
49
|
const isActiveAction = isControllerExtensionEnabledForControl(control, isReuseComponent, this.context.flexSettings.isCloud);
|
|
50
50
|
this.control = isActiveAction ? control : undefined;
|
|
51
51
|
}
|
|
@@ -60,8 +60,8 @@ export class AddControllerToPageQuickAction
|
|
|
60
60
|
)[0];
|
|
61
61
|
if (control) {
|
|
62
62
|
const controlInfo = getControllerInfoForControl(control);
|
|
63
|
-
const data = await getExistingController(controlInfo.controllerName);
|
|
64
|
-
this.controllerExists = data?.
|
|
63
|
+
const data = await getExistingController(controlInfo.controllerName, controlInfo.viewId);
|
|
64
|
+
this.controllerExists = data?.baseControllerExists || data?.instanceControllerExists;
|
|
65
65
|
const isActiveAction = isControllerExtensionEnabledForControl(
|
|
66
66
|
control,
|
|
67
67
|
isReuseComponent,
|
|
@@ -40,8 +40,9 @@ sap.ui.define(["sap/ui/dt/OverlayRegistry", "../../dialog-factory", "../simple-q
|
|
|
40
40
|
if (this.control) {
|
|
41
41
|
const overlay = OverlayRegistry.getOverlay(this.control) || [];
|
|
42
42
|
const controlInfo = getControllerInfoForControl(this.control);
|
|
43
|
-
const data = await getExistingController(controlInfo.controllerName);
|
|
44
|
-
const
|
|
43
|
+
const data = await getExistingController(controlInfo.controllerName, controlInfo.viewId);
|
|
44
|
+
const controllerPathFromRoot = data.baseControllerPathFromRoot || data.instanceControllerPathFromRoot || '';
|
|
45
|
+
const controllerPath = controllerPathFromRoot.replaceAll('/', '.').replace(/\.[^.]+$/, '');
|
|
45
46
|
await DialogFactory.createDialog(overlay, this.context.rta, DialogNames.ADD_ACTION, undefined, {
|
|
46
47
|
title: 'QUICK_ACTION_ADD_CUSTOM_PAGE_ACTION',
|
|
47
48
|
controllerReference: controllerPath ? `.extension.${controllerPath}.<REPLACE_WITH_YOUR_HANDLER_NAME>` : '.extension.<ApplicationId.FolderName.ScriptFilename.methodName>',
|
|
@@ -43,8 +43,9 @@ export class AddPageActionQuickAction extends SimpleQuickActionDefinitionBase im
|
|
|
43
43
|
if (this.control) {
|
|
44
44
|
const overlay = OverlayRegistry.getOverlay(this.control) || [];
|
|
45
45
|
const controlInfo = getControllerInfoForControl(this.control);
|
|
46
|
-
const data = await getExistingController(controlInfo.controllerName);
|
|
47
|
-
const
|
|
46
|
+
const data = await getExistingController(controlInfo.controllerName, controlInfo.viewId);
|
|
47
|
+
const controllerPathFromRoot = data.baseControllerPathFromRoot || data.instanceControllerPathFromRoot || '';
|
|
48
|
+
const controllerPath = controllerPathFromRoot.replaceAll('/', '.').replace(/\.[^.]+$/, '');
|
|
48
49
|
await DialogFactory.createDialog(
|
|
49
50
|
overlay,
|
|
50
51
|
this.context.rta,
|
|
@@ -50,8 +50,9 @@ sap.ui.define(["sap/ui/dt/OverlayRegistry", "../../dialog-factory", "../../../ut
|
|
|
50
50
|
if (table) {
|
|
51
51
|
const overlay = OverlayRegistry.getOverlay(table) || [];
|
|
52
52
|
const controlInfo = getControllerInfoForControl(table);
|
|
53
|
-
const data = await getExistingController(controlInfo.controllerName);
|
|
54
|
-
const
|
|
53
|
+
const data = await getExistingController(controlInfo.controllerName, controlInfo.viewId);
|
|
54
|
+
const controllerPathFromRoot = data.baseControllerPathFromRoot || data.instanceControllerPathFromRoot || '';
|
|
55
|
+
const controllerPath = controllerPathFromRoot.replaceAll('/', '.').replace(/\.[^.]+$/, '');
|
|
55
56
|
await DialogFactory.createDialog(overlay, this.context.rta, DialogNames.ADD_ACTION, undefined, {
|
|
56
57
|
title: 'QUICK_ACTION_ADD_CUSTOM_TABLE_ACTION',
|
|
57
58
|
controllerReference: controllerPath ? `.extension.${controllerPath}.<methodName>` : '.extension.<ApplicationId.FolderName.ScriptFilename.methodName>',
|
|
@@ -52,8 +52,9 @@ export class AddTableActionQuickAction extends TableQuickActionDefinitionBase im
|
|
|
52
52
|
if (table) {
|
|
53
53
|
const overlay = OverlayRegistry.getOverlay(table) || [];
|
|
54
54
|
const controlInfo = getControllerInfoForControl(table);
|
|
55
|
-
const data = await getExistingController(controlInfo.controllerName);
|
|
56
|
-
const
|
|
55
|
+
const data = await getExistingController(controlInfo.controllerName, controlInfo.viewId);
|
|
56
|
+
const controllerPathFromRoot = data.baseControllerPathFromRoot || data.instanceControllerPathFromRoot || '';
|
|
57
|
+
const controllerPath = controllerPathFromRoot.replaceAll('/', '.').replace(/\.[^.]+$/, '');
|
|
57
58
|
await DialogFactory.createDialog(
|
|
58
59
|
overlay,
|
|
59
60
|
this.context.rta,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<Dialog id="controllerExtensionDialog"
|
|
1
|
+
<Dialog id="controllerExtensionDialog"
|
|
2
2
|
xmlns:mvc="sap.ui.core.mvc"
|
|
3
3
|
xmlns="sap.m"
|
|
4
4
|
xmlns:core="sap.ui.core"
|
|
5
|
-
xmlns:f="sap.ui.layout.form"
|
|
6
|
-
title="Extend With Controller"
|
|
7
|
-
contentWidth="450px"
|
|
5
|
+
xmlns:f="sap.ui.layout.form"
|
|
6
|
+
title="Extend With Controller"
|
|
7
|
+
contentWidth="450px"
|
|
8
8
|
class="sapUiRTABorder">
|
|
9
9
|
<content>
|
|
10
10
|
<f:SimpleForm
|
|
@@ -15,12 +15,28 @@
|
|
|
15
15
|
visible="{/inputFormVisibility}">
|
|
16
16
|
<f:content>
|
|
17
17
|
<Label text="Controller Name" />
|
|
18
|
-
<Input
|
|
18
|
+
<Input
|
|
19
19
|
id="controllerName"
|
|
20
20
|
description="{/controllerExtension}"
|
|
21
21
|
value="{/newControllerName}"
|
|
22
22
|
liveChange="onControllerNameInputChange">
|
|
23
23
|
</Input>
|
|
24
|
+
<Label text="{i18n>ADP_CONTROLLER_TYPE_SELECTION_LABEL}"
|
|
25
|
+
visible="{/instanceSpecificVisibility}" />
|
|
26
|
+
<RadioButtonGroup
|
|
27
|
+
id="controllerTypeGroup"
|
|
28
|
+
select="onControllerTypeSelectionChange"
|
|
29
|
+
selectedIndex="{/controllerTypeSelectedIndex}"
|
|
30
|
+
visible="{/instanceSpecificVisibility}">
|
|
31
|
+
<buttons>
|
|
32
|
+
<RadioButton id="baseControllerRadio"
|
|
33
|
+
text="{i18n>ADP_CONTROLLER_TYPE_BASE}"
|
|
34
|
+
enabled="{/baseControllerEnabled}" />
|
|
35
|
+
<RadioButton id="instanceControllerRadio"
|
|
36
|
+
text="{i18n>ADP_CONTROLLER_TYPE_INSTANCE}"
|
|
37
|
+
enabled="{/instanceControllerEnabled}" />
|
|
38
|
+
</buttons>
|
|
39
|
+
</RadioButtonGroup>
|
|
24
40
|
</f:content>
|
|
25
41
|
</f:SimpleForm>
|
|
26
42
|
<f:SimpleForm
|
|
@@ -31,7 +47,18 @@
|
|
|
31
47
|
singleContainerFullSize="false">
|
|
32
48
|
<f:content>
|
|
33
49
|
<Label text="{i18n>ADP_CONTROLLER_EXTENSION_EXISTS}" />
|
|
34
|
-
|
|
50
|
+
|
|
51
|
+
<Label text="{i18n>ADP_CONTROLLER_TYPE_BASE}" visible="{/baseControllerExists}" />
|
|
52
|
+
<Link text="{/baseControllerPathFromRoot}" press="onOpenBaseController"
|
|
53
|
+
visible="{= ${/baseControllerExists} && !${/isRunningInBAS} }" />
|
|
54
|
+
<Text text="{/baseControllerPathFromRoot}"
|
|
55
|
+
visible="{= ${/baseControllerExists} && ${/isRunningInBAS} }" />
|
|
56
|
+
|
|
57
|
+
<Label text="{i18n>ADP_CONTROLLER_TYPE_INSTANCE}" visible="{/instanceControllerExists}" />
|
|
58
|
+
<Link text="{/instanceControllerPathFromRoot}" press="onOpenInstanceController"
|
|
59
|
+
visible="{= ${/instanceControllerExists} && !${/isRunningInBAS} }" />
|
|
60
|
+
<Text text="{/instanceControllerPathFromRoot}"
|
|
61
|
+
visible="{= ${/instanceControllerExists} && ${/isRunningInBAS} }" />
|
|
35
62
|
</f:content>
|
|
36
63
|
</f:SimpleForm>
|
|
37
64
|
<f:SimpleForm
|
package/dist/client/adp/utils.js
CHANGED
|
@@ -107,6 +107,58 @@ sap.ui.define(["sap/ui/fl/Utils", "../utils/core", "../utils/changes", "../utils
|
|
|
107
107
|
return typeof nestedProperty === 'string' ? nestedProperty.includes(propertyValue) : false;
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Collects the `content.viewId` values of all pending `codeExt` changes in the RTA command stack for a
|
|
113
|
+
* given controller. A base page controller extension has no `viewId` (yields `undefined`), while an
|
|
114
|
+
* instance-specific extension yields the ID of the view it is bound to.
|
|
115
|
+
*
|
|
116
|
+
* @param {RuntimeAuthoring} rta - The RuntimeAuthoring instance to inspect.
|
|
117
|
+
* @param {string} controllerName - The controller name to match against `selector.controllerName`.
|
|
118
|
+
* @returns {(string | undefined)[]} The `content.viewId` of each matching pending codeExt change.
|
|
119
|
+
*/
|
|
120
|
+
function getPendingCodeExtViewIds(rta, controllerName) {
|
|
121
|
+
const allCommands = rta.getCommandStack().getCommands();
|
|
122
|
+
const viewIds = [];
|
|
123
|
+
const collectFromCommand = command => {
|
|
124
|
+
getFlexChangeList(command).forEach(change => {
|
|
125
|
+
const changeDefinition = getChangeDefinition(change);
|
|
126
|
+
if (getNestedProperty(changeDefinition, 'changeType') !== 'codeExt') {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (getNestedProperty(changeDefinition, 'selector.controllerName') !== controllerName) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const viewId = getNestedProperty(changeDefinition, 'content.viewId');
|
|
133
|
+
viewIds.push(typeof viewId === 'string' ? viewId : undefined);
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
allCommands.forEach(command => {
|
|
137
|
+
if (typeof command.getCommands === 'function') {
|
|
138
|
+
command.getCommands().forEach(subCommand => {
|
|
139
|
+
if (subCommand?.getProperty('name') === 'codeExt') {
|
|
140
|
+
collectFromCommand(subCommand);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
collectFromCommand(command);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return viewIds;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Resolves the controller name of a view, preferring the typed controller module name and falling
|
|
151
|
+
* back to the controller instance's metadata name. Returns an empty string when the view owns no
|
|
152
|
+
* controller.
|
|
153
|
+
*
|
|
154
|
+
* @param view The view for which to resolve the controller name.
|
|
155
|
+
* @returns The controller name (`module:...` or class name), or an empty string when none is resolvable.
|
|
156
|
+
*/
|
|
157
|
+
function resolveControllerName(view) {
|
|
158
|
+
const moduleName = view?.getControllerModuleName?.();
|
|
159
|
+
return moduleName ? `module:${moduleName}` : view?.getController?.()?.getMetadata?.().getName() ?? '';
|
|
160
|
+
}
|
|
161
|
+
|
|
110
162
|
/**
|
|
111
163
|
* Gets controller name and view ID for the given UI5 control.
|
|
112
164
|
*
|
|
@@ -114,13 +166,28 @@ sap.ui.define(["sap/ui/fl/Utils", "../utils/core", "../utils/changes", "../utils
|
|
|
114
166
|
* @returns The controller name and view ID.
|
|
115
167
|
*/
|
|
116
168
|
function getControllerInfoForControl(control) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
169
|
+
let view = FlexUtils.getViewForControl(control);
|
|
170
|
+
let controllerName = resolveControllerName(view);
|
|
171
|
+
while (!controllerName) {
|
|
172
|
+
const parent = view?.getParent?.();
|
|
173
|
+
if (!parent) {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
const parentView = FlexUtils.getViewForControl(parent);
|
|
177
|
+
if (!parentView || parentView === view) {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
view = parentView;
|
|
181
|
+
controllerName = resolveControllerName(view);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// viewId is intentionally the controller-bearing view's ID (which may be an ancestor of the
|
|
185
|
+
// original view). The change file binds controllerName and viewId as a pair — returning the
|
|
186
|
+
// innermost view's ID when it has no controller would create an inconsistent pair that the
|
|
187
|
+
// server could never match.
|
|
121
188
|
return {
|
|
122
189
|
controllerName,
|
|
123
|
-
viewId
|
|
190
|
+
viewId: view.getId()
|
|
124
191
|
};
|
|
125
192
|
}
|
|
126
193
|
|
|
@@ -189,6 +256,7 @@ sap.ui.define(["sap/ui/fl/Utils", "../utils/core", "../utils/changes", "../utils
|
|
|
189
256
|
__exports.checkForExistingChange = checkForExistingChange;
|
|
190
257
|
__exports.getNestedProperty = getNestedProperty;
|
|
191
258
|
__exports.matchesChangeProperty = matchesChangeProperty;
|
|
259
|
+
__exports.getPendingCodeExtViewIds = getPendingCodeExtViewIds;
|
|
192
260
|
__exports.getControllerInfoForControl = getControllerInfoForControl;
|
|
193
261
|
__exports.getControllerInfo = getControllerInfo;
|
|
194
262
|
__exports.getReuseComponentChecker = getReuseComponentChecker;
|
package/dist/client/adp/utils.ts
CHANGED
|
@@ -113,11 +113,74 @@ export function matchesChangeProperty(command: FlexCommand, propertyPath: string
|
|
|
113
113
|
return typeof nestedProperty === 'string' ? nestedProperty.includes(propertyValue) : false;
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Collects the `content.viewId` values of all pending `codeExt` changes in the RTA command stack for a
|
|
119
|
+
* given controller. A base page controller extension has no `viewId` (yields `undefined`), while an
|
|
120
|
+
* instance-specific extension yields the ID of the view it is bound to.
|
|
121
|
+
*
|
|
122
|
+
* @param {RuntimeAuthoring} rta - The RuntimeAuthoring instance to inspect.
|
|
123
|
+
* @param {string} controllerName - The controller name to match against `selector.controllerName`.
|
|
124
|
+
* @returns {(string | undefined)[]} The `content.viewId` of each matching pending codeExt change.
|
|
125
|
+
*/
|
|
126
|
+
export function getPendingCodeExtViewIds(rta: RuntimeAuthoring, controllerName: string): (string | undefined)[] {
|
|
127
|
+
const allCommands = rta.getCommandStack().getCommands();
|
|
128
|
+
const viewIds: (string | undefined)[] = [];
|
|
129
|
+
|
|
130
|
+
const collectFromCommand = (command: FlexCommand): void => {
|
|
131
|
+
getFlexChangeList(command).forEach((change) => {
|
|
132
|
+
const changeDefinition = getChangeDefinition(change);
|
|
133
|
+
if (getNestedProperty(changeDefinition, 'changeType') !== 'codeExt') {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (getNestedProperty(changeDefinition, 'selector.controllerName') !== controllerName) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const viewId = getNestedProperty(changeDefinition, 'content.viewId');
|
|
140
|
+
viewIds.push(typeof viewId === 'string' ? viewId : undefined);
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
allCommands.forEach((command: FlexCommand) => {
|
|
145
|
+
if (typeof command.getCommands === 'function') {
|
|
146
|
+
command.getCommands().forEach((subCommand: FlexCommand) => {
|
|
147
|
+
if (subCommand?.getProperty('name') === 'codeExt') {
|
|
148
|
+
collectFromCommand(subCommand);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
} else {
|
|
152
|
+
collectFromCommand(command);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return viewIds;
|
|
157
|
+
}
|
|
158
|
+
|
|
116
159
|
interface ControllerInfo {
|
|
117
160
|
controllerName: string;
|
|
118
161
|
viewId: string;
|
|
119
162
|
}
|
|
120
163
|
|
|
164
|
+
interface ViewLike {
|
|
165
|
+
getId(): string;
|
|
166
|
+
getControllerModuleName?(): string | undefined;
|
|
167
|
+
getController?(): { getMetadata(): { getName(): string } } | undefined;
|
|
168
|
+
getParent?(): ManagedObject | null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Resolves the controller name of a view, preferring the typed controller module name and falling
|
|
173
|
+
* back to the controller instance's metadata name. Returns an empty string when the view owns no
|
|
174
|
+
* controller.
|
|
175
|
+
*
|
|
176
|
+
* @param view The view for which to resolve the controller name.
|
|
177
|
+
* @returns The controller name (`module:...` or class name), or an empty string when none is resolvable.
|
|
178
|
+
*/
|
|
179
|
+
function resolveControllerName(view: ViewLike): string {
|
|
180
|
+
const moduleName = view?.getControllerModuleName?.();
|
|
181
|
+
return moduleName ? `module:${moduleName}` : view?.getController?.()?.getMetadata?.().getName() ?? '';
|
|
182
|
+
}
|
|
183
|
+
|
|
121
184
|
/**
|
|
122
185
|
* Gets controller name and view ID for the given UI5 control.
|
|
123
186
|
*
|
|
@@ -125,11 +188,27 @@ interface ControllerInfo {
|
|
|
125
188
|
* @returns The controller name and view ID.
|
|
126
189
|
*/
|
|
127
190
|
export function getControllerInfoForControl(control: ManagedObject): ControllerInfo {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
191
|
+
let view = FlexUtils.getViewForControl(control) as ViewLike;
|
|
192
|
+
let controllerName = resolveControllerName(view);
|
|
193
|
+
|
|
194
|
+
while (!controllerName) {
|
|
195
|
+
const parent = view?.getParent?.();
|
|
196
|
+
if (!parent) {
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
const parentView = FlexUtils.getViewForControl(parent) as ViewLike;
|
|
200
|
+
if (!parentView || parentView === view) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
view = parentView;
|
|
204
|
+
controllerName = resolveControllerName(view);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// viewId is intentionally the controller-bearing view's ID (which may be an ancestor of the
|
|
208
|
+
// original view). The change file binds controllerName and viewId as a pair — returning the
|
|
209
|
+
// innermost view's ID when it has no controller would create an inconsistent pair that the
|
|
210
|
+
// server could never match.
|
|
211
|
+
return { controllerName, viewId: view.getId() };
|
|
133
212
|
}
|
|
134
213
|
|
|
135
214
|
/**
|