@sap-ux/preview-middleware 1.2.0 → 1.2.2
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/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/messagebundle.properties +3 -0
- package/package.json +6 -6
|
@@ -125,12 +125,16 @@ sap.ui.define(["../utils/error"], function (___utils_errorjs) {
|
|
|
125
125
|
* Checks for existing controller in the project's workspace
|
|
126
126
|
*
|
|
127
127
|
* @param controllerName Name of the controller
|
|
128
|
+
* @param viewId ID of the current view, used to detect an existing instance-specific extension
|
|
128
129
|
* @returns {CodeExtResponse} Returns path to existing controller if found
|
|
129
130
|
*/
|
|
130
|
-
async function getExistingController(controllerName) {
|
|
131
|
+
async function getExistingController(controllerName, viewId) {
|
|
131
132
|
const params = new URLSearchParams({
|
|
132
133
|
name: controllerName
|
|
133
134
|
});
|
|
135
|
+
if (viewId) {
|
|
136
|
+
params.append('viewId', viewId);
|
|
137
|
+
}
|
|
134
138
|
const url = `${ApiEndpoints.CODE_EXT}?${params.toString()}`;
|
|
135
139
|
return request(url, RequestMethod.GET);
|
|
136
140
|
}
|
|
@@ -31,9 +31,12 @@ export interface FragmentsResponse {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface CodeExtResponse {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
baseControllerExists: boolean;
|
|
35
|
+
baseControllerPath: string;
|
|
36
|
+
baseControllerPathFromRoot: string;
|
|
37
|
+
instanceControllerExists: boolean;
|
|
38
|
+
instanceControllerPath: string;
|
|
39
|
+
instanceControllerPathFromRoot: string;
|
|
37
40
|
isRunningInBAS: boolean;
|
|
38
41
|
isTsSupported: boolean;
|
|
39
42
|
}
|
|
@@ -172,10 +175,14 @@ export async function getDataSourceAnnotationFileMap(): Promise<AnnotationDataSo
|
|
|
172
175
|
* Checks for existing controller in the project's workspace
|
|
173
176
|
*
|
|
174
177
|
* @param controllerName Name of the controller
|
|
178
|
+
* @param viewId ID of the current view, used to detect an existing instance-specific extension
|
|
175
179
|
* @returns {CodeExtResponse} Returns path to existing controller if found
|
|
176
180
|
*/
|
|
177
|
-
export async function getExistingController(controllerName: string): Promise<CodeExtResponse> {
|
|
181
|
+
export async function getExistingController(controllerName: string, viewId?: string): Promise<CodeExtResponse> {
|
|
178
182
|
const params = new URLSearchParams({ name: controllerName });
|
|
183
|
+
if (viewId) {
|
|
184
|
+
params.append('viewId', viewId);
|
|
185
|
+
}
|
|
179
186
|
const url = `${ApiEndpoints.CODE_EXT}?${params.toString()}` as ApiEndpoints;
|
|
180
187
|
return request<CodeExtResponse>(url, RequestMethod.GET);
|
|
181
188
|
}
|
|
@@ -25,6 +25,7 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
25
25
|
const CommandExecutor = _interopRequireDefault(__CommandExecutor);
|
|
26
26
|
const checkForExistingChange = ___utilsjs["checkForExistingChange"];
|
|
27
27
|
const getControllerInfo = ___utilsjs["getControllerInfo"];
|
|
28
|
+
const getPendingCodeExtViewIds = ___utilsjs["getPendingCodeExtViewIds"];
|
|
28
29
|
const BaseDialog = _interopRequireDefault(__BaseDialog);
|
|
29
30
|
/**
|
|
30
31
|
* @namespace open.ux.preview.client.adp.controllers
|
|
@@ -47,6 +48,7 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
47
48
|
this.setEscapeHandler();
|
|
48
49
|
const resourceModel = await getResourceModel('open.ux.preview.client');
|
|
49
50
|
this.bundle = await getTextBundle();
|
|
51
|
+
this.ui5Version = await getUi5Version();
|
|
50
52
|
await this.buildDialogData();
|
|
51
53
|
this.dialog.setModel(resourceModel, 'i18n');
|
|
52
54
|
this.dialog.setModel(this.model);
|
|
@@ -106,16 +108,18 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
106
108
|
source.setEnabled(false);
|
|
107
109
|
const controllerName = this.model.getProperty('/newControllerName');
|
|
108
110
|
const viewId = this.model.getProperty('/viewId');
|
|
111
|
+
const isInstanceSpecific = this.model.getProperty('/isInstanceSpecific');
|
|
109
112
|
const controllerRef = {
|
|
110
113
|
codeRef: `coding/${controllerName}.js`,
|
|
111
|
-
viewId
|
|
114
|
+
viewId,
|
|
115
|
+
instanceSpecific: isInstanceSpecific
|
|
112
116
|
};
|
|
113
117
|
if (this.data) {
|
|
114
118
|
this.data.deferred.resolve(controllerRef);
|
|
115
119
|
} else {
|
|
116
120
|
await this.createNewController(controllerName, controllerRef);
|
|
117
121
|
}
|
|
118
|
-
if (this.data &&
|
|
122
|
+
if (this.data && this.isControllerExtensionSupported()) {
|
|
119
123
|
await sendInfoCenterMessage({
|
|
120
124
|
title: {
|
|
121
125
|
key: 'ADP_CREATE_CONTROLLER_EXTENSION_TITLE'
|
|
@@ -127,9 +131,6 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
127
131
|
type: MessageBarType.info
|
|
128
132
|
});
|
|
129
133
|
}
|
|
130
|
-
} else {
|
|
131
|
-
const controllerPath = this.model.getProperty('/controllerPath');
|
|
132
|
-
window.open(`vscode://file${controllerPath}`);
|
|
133
134
|
}
|
|
134
135
|
this.handleDialogClose();
|
|
135
136
|
},
|
|
@@ -143,41 +144,62 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
143
144
|
controllerName,
|
|
144
145
|
viewId
|
|
145
146
|
} = getControllerInfo(overlayControl);
|
|
146
|
-
const data = await this.getExistingController(controllerName);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
const data = await this.getExistingController(controllerName, viewId);
|
|
148
|
+
if (!data) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Combine persisted (server) and pending (command stack) changes to determine whether a base
|
|
153
|
+
// page controller and/or an instance-specific controller for this view already exist.
|
|
154
|
+
const pendingViewIds = getPendingCodeExtViewIds(this.rta, controllerName);
|
|
155
|
+
const baseExists = data.baseControllerExists || pendingViewIds.some(id => !id);
|
|
156
|
+
const instanceExists = data.instanceControllerExists || pendingViewIds.includes(viewId);
|
|
157
|
+
const showInstanceSpecificOption = this.isInstanceSpecificSupported();
|
|
158
|
+
if (!showInstanceSpecificOption) {
|
|
159
|
+
if (pendingViewIds.length > 0) {
|
|
150
160
|
this.updateModelForExistingPendingChange();
|
|
151
|
-
} else if (data
|
|
152
|
-
this.updateModelForExistingController(data);
|
|
161
|
+
} else if (data.baseControllerExists) {
|
|
162
|
+
this.updateModelForExistingController(data, true);
|
|
153
163
|
} else {
|
|
154
|
-
this.updateModelForNewController(viewId, data.isTsSupported);
|
|
164
|
+
this.updateModelForNewController(viewId, data.isTsSupported, false, false, false);
|
|
155
165
|
await this.getControllers();
|
|
156
166
|
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (baseExists && instanceExists) {
|
|
170
|
+
if (data.baseControllerExists || data.instanceControllerExists) {
|
|
171
|
+
this.updateModelForExistingController(data);
|
|
172
|
+
} else {
|
|
173
|
+
this.updateModelForExistingPendingChange();
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
157
176
|
}
|
|
177
|
+
this.updateModelForNewController(viewId, data.isTsSupported, true, baseExists, instanceExists);
|
|
178
|
+
await this.getControllers();
|
|
158
179
|
},
|
|
159
180
|
/**
|
|
160
|
-
* Updates the model properties for
|
|
181
|
+
* Updates the model properties for existing controller(s).
|
|
182
|
+
* Shows all persisted controllers (base and/or instance) in the existing-controller form, each with its own link to open in VS Code.
|
|
161
183
|
*
|
|
162
|
-
* @param
|
|
184
|
+
* @param data - Server response containing existence flags and file paths.
|
|
185
|
+
* @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.
|
|
163
186
|
*/
|
|
164
|
-
updateModelForExistingController: function _updateModelForExistingController(data) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
this.model.setProperty('/
|
|
172
|
-
this.model.setProperty('/
|
|
173
|
-
this.model.setProperty('/controllerPathFromRoot', controllerPathFromRoot);
|
|
187
|
+
updateModelForExistingController: function _updateModelForExistingController(data, showVsCodeButton = false) {
|
|
188
|
+
this.model.setProperty('/controllerExists', true);
|
|
189
|
+
this.model.setProperty('/baseControllerExists', data.baseControllerExists);
|
|
190
|
+
this.model.setProperty('/baseControllerPath', data.baseControllerPath);
|
|
191
|
+
this.model.setProperty('/baseControllerPathFromRoot', data.baseControllerPathFromRoot);
|
|
192
|
+
this.model.setProperty('/instanceControllerExists', data.instanceControllerExists);
|
|
193
|
+
this.model.setProperty('/instanceControllerPath', data.instanceControllerPath);
|
|
194
|
+
this.model.setProperty('/instanceControllerPathFromRoot', data.instanceControllerPathFromRoot);
|
|
195
|
+
this.model.setProperty('/isRunningInBAS', data.isRunningInBAS);
|
|
174
196
|
this.model.setProperty('/inputFormVisibility', false);
|
|
175
197
|
this.model.setProperty('/pendingChangeFormVisibility', false);
|
|
176
198
|
this.model.setProperty('/existingControllerFormVisibility', true);
|
|
177
|
-
if (isRunningInBAS) {
|
|
178
|
-
this.dialog.getBeginButton().setVisible(false);
|
|
179
|
-
} else {
|
|
199
|
+
if (showVsCodeButton && !data.isRunningInBAS) {
|
|
180
200
|
this.dialog.getBeginButton().setText('Open in VS Code').setEnabled(true);
|
|
201
|
+
} else {
|
|
202
|
+
this.dialog.getBeginButton().setVisible(false);
|
|
181
203
|
}
|
|
182
204
|
this.dialog.getEndButton().setText('Close');
|
|
183
205
|
},
|
|
@@ -196,24 +218,34 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
196
218
|
*
|
|
197
219
|
* @param {string} viewId - The view ID.
|
|
198
220
|
* @param {boolean} isTsSupported - Whether TypeScript supported for the current project.
|
|
221
|
+
* @param {boolean} showInstanceSpecificOption - Whether to show the instance-specific radio button option.
|
|
222
|
+
* @param {boolean} baseExists - Whether a base page controller extension already exists.
|
|
223
|
+
* @param {boolean} instanceExists - Whether an instance-specific extension already exists for this view.
|
|
199
224
|
*/
|
|
200
|
-
updateModelForNewController: function _updateModelForNewController(viewId, isTsSupported) {
|
|
225
|
+
updateModelForNewController: function _updateModelForNewController(viewId, isTsSupported, showInstanceSpecificOption, baseExists, instanceExists) {
|
|
201
226
|
this.model.setProperty('/viewId', viewId);
|
|
202
227
|
this.model.setProperty('/controllerExtension', isTsSupported ? '.ts' : '.js');
|
|
203
228
|
this.model.setProperty('/existingControllerFormVisibility', false);
|
|
204
229
|
this.model.setProperty('/pendingChangeFormVisibility', false);
|
|
205
230
|
this.model.setProperty('/inputFormVisibility', true);
|
|
231
|
+
this.model.setProperty('/instanceSpecificVisibility', showInstanceSpecificOption);
|
|
232
|
+
this.model.setProperty('/baseControllerEnabled', !baseExists);
|
|
233
|
+
this.model.setProperty('/instanceControllerEnabled', !instanceExists);
|
|
234
|
+
const selectedIndex = baseExists ? 1 : 0;
|
|
235
|
+
this.model.setProperty('/controllerTypeSelectedIndex', selectedIndex);
|
|
236
|
+
this.model.setProperty('/isInstanceSpecific', selectedIndex === 1);
|
|
206
237
|
},
|
|
207
238
|
/**
|
|
208
239
|
* Retrieves existing controller data if found in the project's workspace.
|
|
209
240
|
*
|
|
210
241
|
* @param controllerName Controller name that exists in the view.
|
|
242
|
+
* @param viewId ID of the current view, used to detect an existing instance-specific extension.
|
|
211
243
|
* @returns Returns existing controller data.
|
|
212
244
|
*/
|
|
213
|
-
getExistingController: async function _getExistingController(controllerName) {
|
|
245
|
+
getExistingController: async function _getExistingController(controllerName, viewId) {
|
|
214
246
|
let data;
|
|
215
247
|
try {
|
|
216
|
-
data = await getExistingController(controllerName);
|
|
248
|
+
data = await getExistingController(controllerName, viewId);
|
|
217
249
|
} catch (e) {
|
|
218
250
|
const error = getError(e);
|
|
219
251
|
await sendInfoCenterMessage({
|
|
@@ -255,7 +287,7 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
255
287
|
* @param controllerRef Controller reference
|
|
256
288
|
*/
|
|
257
289
|
createNewController: async function _createNewController(controllerName, controllerRef) {
|
|
258
|
-
if (
|
|
290
|
+
if (this.isControllerExtensionSupported()) {
|
|
259
291
|
await this.createControllerCommand(controllerName, controllerRef);
|
|
260
292
|
return;
|
|
261
293
|
}
|
|
@@ -264,7 +296,7 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
264
296
|
controllerName
|
|
265
297
|
});
|
|
266
298
|
const service = await this.rta.getService('controllerExtension');
|
|
267
|
-
const change = await service.add(controllerRef.codeRef, controllerRef.viewId);
|
|
299
|
+
const change = await service.add(controllerRef.codeRef, controllerRef.viewId, controllerRef.instanceSpecific);
|
|
268
300
|
change.creation = new Date().toISOString();
|
|
269
301
|
await writeChange(change);
|
|
270
302
|
await sendInfoCenterMessage({
|
|
@@ -315,9 +347,32 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
315
347
|
type: MessageBarType.info
|
|
316
348
|
});
|
|
317
349
|
},
|
|
318
|
-
isControllerExtensionSupported:
|
|
319
|
-
|
|
320
|
-
|
|
350
|
+
isControllerExtensionSupported: function _isControllerExtensionSupported() {
|
|
351
|
+
return !isLowerThanMinimalUi5Version(this.ui5Version, ControllerExtension.CONTROLLER_EXT_MIN_UI5_VERSION);
|
|
352
|
+
},
|
|
353
|
+
/**
|
|
354
|
+
* Handles the selection change on the controller type for the radio button group.
|
|
355
|
+
*
|
|
356
|
+
* @param event Event
|
|
357
|
+
*/
|
|
358
|
+
onControllerTypeSelectionChange: function _onControllerTypeSelectionChange(event) {
|
|
359
|
+
const group = event.getSource();
|
|
360
|
+
this.model.setProperty('/isInstanceSpecific', group.getSelectedIndex() === 1);
|
|
361
|
+
},
|
|
362
|
+
/**
|
|
363
|
+
* Opens the base page controller extension file in VS Code.
|
|
364
|
+
*/
|
|
365
|
+
onOpenBaseController: function _onOpenBaseController() {
|
|
366
|
+
window.open(`vscode://file${this.model.getProperty('/baseControllerPath')}`);
|
|
367
|
+
},
|
|
368
|
+
/**
|
|
369
|
+
* Opens the instance-specific controller extension file in VS Code.
|
|
370
|
+
*/
|
|
371
|
+
onOpenInstanceController: function _onOpenInstanceController() {
|
|
372
|
+
window.open(`vscode://file${this.model.getProperty('/instanceControllerPath')}`);
|
|
373
|
+
},
|
|
374
|
+
isInstanceSpecificSupported: function _isInstanceSpecificSupported() {
|
|
375
|
+
return !isLowerThanMinimalUi5Version(this.ui5Version, ControllerExtension.INSTANCE_SPECIFIC_MIN_UI5_VERSION);
|
|
321
376
|
}
|
|
322
377
|
});
|
|
323
378
|
/* The minimum version of UI5 framework which supports controller extensions. */
|
|
@@ -325,6 +380,11 @@ sap.ui.define(["sap/ui/core/library", "sap/ui/model/json/JSONModel", "open/ux/pr
|
|
|
325
380
|
major: 1,
|
|
326
381
|
minor: 135
|
|
327
382
|
};
|
|
383
|
+
/* The minimum version of UI5 framework which supports instance-specific controller extensions. */
|
|
384
|
+
ControllerExtension.INSTANCE_SPECIFIC_MIN_UI5_VERSION = {
|
|
385
|
+
major: 1,
|
|
386
|
+
minor: 143
|
|
387
|
+
};
|
|
328
388
|
return ControllerExtension;
|
|
329
389
|
});
|
|
330
390
|
//# sourceMappingURL=ControllerExtension.controller.js.map
|
|
@@ -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
|
/**
|
|
@@ -42,6 +42,9 @@ ADP_ADD_FRAGMENT_WITH_TEMPLATE_NOTIFICATION = Note: The `{0}.fragment.xml` fragm
|
|
|
42
42
|
ADP_CONTROLLER_EXTENSION_EXISTS = An existing controller extension has been found. Please use the previously created extension controller.
|
|
43
43
|
ADP_CONTROLLER_PENDING_CHANGE_EXISTS = A pending change for controller extension has been found. Please use the previously created extension controller after saving the change or delete the pending change.
|
|
44
44
|
ADP_CREATE_CONTROLLER_EXTENSION = Note: The `{0}` controller extension will be created once you save the change.
|
|
45
|
+
ADP_CONTROLLER_TYPE_SELECTION_LABEL = Which controller would you like to extend?
|
|
46
|
+
ADP_CONTROLLER_TYPE_BASE = Base Page Controller
|
|
47
|
+
ADP_CONTROLLER_TYPE_INSTANCE = This entity specific controller
|
|
45
48
|
ADP_ADD_TWO_FRAGMENTS_WITH_TEMPLATE_NOTIFICATION = Note: The `{0}.fragment.xml` and `{1}.fragment.xml` fragments will be created once you save the changes.
|
|
46
49
|
ADP_SYNC_VIEWS_MESSAGE = Synchronous views are detected for this application. Controller extensions are not supported for such views and will be disabled.
|
|
47
50
|
ADP_QUICK_ACTION_DIALOG_OPEN_MESSAGE = This action is disabled because a dialog is already open.
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"bugs": {
|
|
11
11
|
"url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Apreview-middleware"
|
|
12
12
|
},
|
|
13
|
-
"version": "1.2.
|
|
13
|
+
"version": "1.2.2",
|
|
14
14
|
"license": "Apache-2.0",
|
|
15
15
|
"author": "@SAP/ux-tools-team",
|
|
16
16
|
"main": "dist/index.js",
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"mem-fs-editor": "9.4.0",
|
|
29
29
|
"qrcode": "1.5.4",
|
|
30
30
|
"@sap/bas-sdk": "3.13.10",
|
|
31
|
-
"@sap-ux/adp-tooling": "1.0.
|
|
31
|
+
"@sap-ux/adp-tooling": "1.0.45",
|
|
32
32
|
"@sap-ux/btp-utils": "2.0.6",
|
|
33
33
|
"@sap-ux/control-property-editor-sources": "npm:@sap-ux/control-property-editor@1.0.10",
|
|
34
34
|
"@sap-ux/feature-toggle": "1.0.5",
|
|
35
35
|
"@sap-ux/logger": "1.0.3",
|
|
36
|
-
"@sap-ux/project-access": "2.1.
|
|
37
|
-
"@sap-ux/system-access": "1.0.
|
|
36
|
+
"@sap-ux/project-access": "2.1.11",
|
|
37
|
+
"@sap-ux/system-access": "1.0.11",
|
|
38
38
|
"@sap-ux/i18n": "1.0.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"nock": "14.0.16",
|
|
55
55
|
"npm-run-all2": "9.0.2",
|
|
56
56
|
"supertest": "7.2.2",
|
|
57
|
-
"@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@1.2.
|
|
57
|
+
"@private/preview-middleware-client": "npm:@sap-ux-private/preview-middleware-client@1.2.2",
|
|
58
58
|
"@sap-ux-private/playwright": "1.0.6",
|
|
59
|
-
"@sap-ux/axios-extension": "2.0.
|
|
59
|
+
"@sap-ux/axios-extension": "2.0.9",
|
|
60
60
|
"@sap-ux/store": "2.0.6",
|
|
61
61
|
"@sap-ux/ui5-info": "1.0.6"
|
|
62
62
|
},
|