codeapp-js 0.3.0 → 1.0.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.
Files changed (158) hide show
  1. package/AI/codeapp.agent.md +105 -0
  2. package/AI/skills/connections/SKILL.md +47 -0
  3. package/AI/skills/dataverse/SKILL.md +99 -0
  4. package/AI/skills/environment-variables/SKILL.md +89 -0
  5. package/AI/skills/frontend-design/SKILL.md +34 -0
  6. package/AI/skills/jira/SKILL.md +81 -0
  7. package/AI/skills/office365-groups/SKILL.md +61 -0
  8. package/AI/skills/office365-outlook/SKILL.md +52 -0
  9. package/AI/skills/office365-users/SKILL.md +78 -0
  10. package/AI/skills/sharepoint/SKILL.md +77 -0
  11. package/AI/skills/sql/SKILL.md +85 -0
  12. package/AI/skills/start/SKILL.md +46 -0
  13. package/AI/skills/teams/SKILL.md +55 -0
  14. package/{examples/combined demo/.power/schemas/office365groups/office365groups.Schema.json → codeApp/.power/schemas/office365groups/office365groups.Schema.json} +2203 -2203
  15. package/codeApp/dist/codeapp.js +95 -1792
  16. package/codeApp/dist/connectors/azureKeyvault.js +459 -0
  17. package/codeApp/dist/connectors/jira.js +1247 -0
  18. package/codeApp/dist/connectors/office365groups.js +642 -0
  19. package/codeApp/dist/connectors/office365users.js +513 -0
  20. package/codeApp/dist/connectors/outlook.js +1393 -0
  21. package/{dev files/sharepoint.js → codeApp/dist/connectors/sharepoint.js} +239 -112
  22. package/codeApp/dist/connectors/sql.js +149 -0
  23. package/codeApp/dist/connectors/teams.js +280 -0
  24. package/codeApp/dist/power-apps-data.js +16 -2
  25. package/examples/{kanban → apps/kanban}/dist/dataverse.js +94 -94
  26. package/examples/{kanban → apps/kanban}/dist/environmentVar.js +55 -55
  27. package/examples/{kanban → apps/kanban}/dist/index.css +605 -605
  28. package/examples/{kanban → apps/kanban}/dist/index.html +21 -21
  29. package/examples/{kanban → apps/kanban}/dist/index.js +860 -860
  30. package/examples/{kanban → apps/kanban}/dist/office365groups.js +97 -97
  31. package/examples/apps/kanban/dist/office365users.js +451 -0
  32. package/examples/{kanban → apps/kanban}/dist/outlook.js +162 -162
  33. package/examples/{planning Poker/dist/power-apps-data.js → apps/kanban/dist/power-apps-data.js} +2953 -2953
  34. package/examples/{kanban → apps/kanban}/dist/sharepoint.js +435 -339
  35. package/examples/{kanban → apps/kanban}/power.config.json +35 -35
  36. package/examples/{planning Poker → apps/planning Poker}/additional files/customizations (tables).xml +6428 -6428
  37. package/examples/{planning Poker → apps/planning Poker}/additional files/dataverse-tables.json +165 -165
  38. package/examples/{planning Poker → apps/planning Poker}/additional files/readme.md +122 -122
  39. package/examples/{planning Poker → apps/planning Poker}/dist/dataverse.js +78 -78
  40. package/examples/{planning Poker → apps/planning Poker}/dist/index.html +198 -198
  41. package/examples/{planning Poker → apps/planning Poker}/dist/index.js +954 -954
  42. package/examples/{todo/dist/power-apps-data.js → apps/planning Poker/dist/power-apps-data.js } +2953 -2953
  43. package/examples/{planning Poker → apps/planning Poker}/dist/styles.css +815 -815
  44. package/examples/{planning Poker → apps/planning Poker}/power.config.json +50 -50
  45. package/examples/{outlook Demo2 → apps/solution explorer}/dist/codeapp.js +9 -245
  46. package/examples/apps/solution explorer/dist/index.html +80 -0
  47. package/examples/apps/solution explorer/dist/index.js +735 -0
  48. package/examples/apps/solution explorer/dist/styles.css +571 -0
  49. package/examples/apps/solution explorer/power.config.json +151 -0
  50. package/examples/{todo → apps/todo}/dist/dataverse.js +64 -64
  51. package/examples/{todo → apps/todo}/dist/index.html +75 -75
  52. package/examples/{todo → apps/todo}/dist/index.js +8 -8
  53. package/examples/{kanban → apps/todo}/dist/power-apps-data.js +2953 -2953
  54. package/examples/{todo → apps/todo}/dist/renderer.js +375 -375
  55. package/examples/{todo → apps/todo}/dist/styles.css +691 -691
  56. package/examples/{todo → apps/todo}/power.config.json +34 -34
  57. package/examples/combined demo/.power/schemas/appschemas/dataSourcesInfo.ts +6275 -7830
  58. package/examples/combined demo/.power/schemas/jira/jira.Schema.json +6903 -0
  59. package/examples/combined demo/.power/schemas/keyvault/keyvault.Schema.json +1600 -0
  60. package/examples/combined demo/.power/schemas/teams/teams.Schema.json +11112 -0
  61. package/examples/combined demo/dist/codeapp.js +394 -1098
  62. package/examples/{outlook Demo2/OutlookDemo_1_0_0_1.zip → combined demo/dist/icon-512.png} +0 -0
  63. package/examples/combined demo/dist/index.html +29 -511
  64. package/examples/combined demo/dist/index.js +490 -470
  65. package/examples/combined demo/dist/office365users.js +513 -0
  66. package/examples/combined demo/dist/outlook.js +1393 -0
  67. package/examples/combined demo/dist/power-apps-data.js +3079 -3006
  68. package/examples/combined demo/dist/styles.css +483 -0
  69. package/examples/combined demo/power.config.json +33 -42
  70. package/examples/combined demo/src/generated/index.ts +12 -14
  71. package/examples/combined demo/src/generated/models/AzureKeyVaultModel.ts +107 -0
  72. package/examples/combined demo/src/generated/models/JiraModel.ts +501 -0
  73. package/examples/combined demo/src/generated/services/AzureKeyVaultService.ts +257 -0
  74. package/examples/combined demo/src/generated/services/JiraService.ts +1124 -0
  75. package/examples/dataverse Demo/dist/codeapp.js +394 -1085
  76. package/examples/dataverse Demo/dist/icon-512.png +0 -0
  77. package/examples/dataverse Demo/dist/index.html +146 -54
  78. package/examples/dataverse Demo/dist/index.js +693 -83
  79. package/examples/dataverse Demo/dist/power-apps-data.js +3079 -2911
  80. package/examples/dataverse Demo/dist/styles.css +528 -0
  81. package/examples/dataverse Demo/power.config.json +41 -35
  82. package/examples/dataverse Demo/readme.md +79 -79
  83. package/examples/groups Demo/dist/codeapp.js +394 -1085
  84. package/examples/groups Demo/dist/icon-512.png +0 -0
  85. package/examples/groups Demo/dist/index.html +21 -25
  86. package/examples/groups Demo/dist/index.js +304 -113
  87. package/examples/groups Demo/dist/office365groups.js +642 -0
  88. package/examples/groups Demo/dist/power-apps-data.js +3079 -2911
  89. package/examples/groups Demo/dist/styles.css +509 -0
  90. package/examples/groups Demo/power.config.json +25 -25
  91. package/examples/myProfile/dist/codeapp.js +398 -0
  92. package/examples/myProfile/dist/index.html +21 -184
  93. package/examples/myProfile/dist/index.js +324 -141
  94. package/examples/myProfile/dist/office365users.js +517 -169
  95. package/examples/myProfile/dist/power-apps-data.js +3080 -2953
  96. package/examples/myProfile/dist/styles.css +458 -0
  97. package/examples/myProfile/power.config.json +24 -23
  98. package/examples/outlook Demo/dist/codeapp.js +394 -1085
  99. package/examples/outlook Demo/dist/index.html +150 -35
  100. package/examples/outlook Demo/dist/index.js +516 -170
  101. package/examples/outlook Demo/dist/outlook.js +1393 -121
  102. package/examples/outlook Demo/dist/power-apps-data.js +3079 -2911
  103. package/examples/outlook Demo/dist/styles.css +408 -84
  104. package/examples/outlook Demo/power.config.json +24 -23
  105. package/examples/outlook Demo/readme.md +92 -82
  106. package/examples/sharePoint Demo/dist/codeapp.js +394 -1085
  107. package/examples/sharePoint Demo/dist/icon-512.png +0 -0
  108. package/examples/sharePoint Demo/dist/index.html +22 -255
  109. package/examples/sharePoint Demo/dist/index.js +899 -262
  110. package/examples/sharePoint Demo/dist/power-apps-data.js +3079 -2911
  111. package/examples/sharePoint Demo/dist/sharepoint.js +466 -0
  112. package/examples/sharePoint Demo/dist/styles.css +587 -0
  113. package/examples/sharePoint Demo/power.config.json +23 -22
  114. package/package.json +1 -1
  115. package/readme.md +465 -76
  116. package/.vscode/settings.json +0 -6
  117. package/dev files/customConnector.js +0 -98
  118. package/dev files/dataverse.js +0 -120
  119. package/dev files/environmentVar.js +0 -55
  120. package/dev files/office365groups.js +0 -65
  121. package/dev files/office365users.js +0 -169
  122. package/dev files/outlook.js +0 -330
  123. package/dev files/power-apps-data.js +0 -2952
  124. package/examples/combined demo/.power/schemas/office365/office365.Schema.json +0 -21098
  125. package/examples/combined demo/.power/schemas/office365users/office365users.Schema.json +0 -2094
  126. package/examples/kanban/agent/decision-log.md +0 -9
  127. package/examples/kanban/agent/mockup-01-editorial-glass.html +0 -159
  128. package/examples/kanban/agent/mockup-02-dark-rail.html +0 -147
  129. package/examples/kanban/agent/mockup-03-paper-grid.html +0 -114
  130. package/examples/kanban/agent/mockup-04-neon-minimal.html +0 -141
  131. package/examples/kanban/agent/mockup-05-mono-architect.html +0 -119
  132. package/examples/kanban/dist/office365users.js +0 -169
  133. package/examples/kanban/src/generated/index.ts +0 -14
  134. package/examples/kanban/src/generated/models/Office365GroupsModel.ts +0 -363
  135. package/examples/kanban/src/generated/models/Office365OutlookModel.ts +0 -2046
  136. package/examples/kanban/src/generated/models/Office365UsersModel.ts +0 -254
  137. package/examples/kanban/src/generated/services/Office365GroupsService.ts +0 -326
  138. package/examples/kanban/src/generated/services/Office365OutlookService.ts +0 -2476
  139. package/examples/kanban/src/generated/services/Office365UsersService.ts +0 -358
  140. package/examples/outlook Demo2/agent/decision-log.md +0 -7
  141. package/examples/outlook Demo2/dist/index.html +0 -98
  142. package/examples/outlook Demo2/dist/index.js +0 -272
  143. package/examples/outlook Demo2/dist/styles.css +0 -639
  144. package/examples/outlook Demo2/power.config.json +0 -23
  145. package/examples/planning Poker/.vscode/settings.json +0 -5
  146. package/examples/sharePoint Demo/agent/decision-log.md +0 -17
  147. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/index.ts +0 -0
  148. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365GroupsModel.ts +0 -0
  149. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365OutlookModel.ts +0 -0
  150. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365UsersModel.ts +0 -0
  151. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365GroupsService.ts +0 -0
  152. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365OutlookService.ts +0 -0
  153. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365UsersService.ts +0 -0
  154. /package/examples/{planning Poker → apps/planning Poker}/additional files/AgilePoker_1_0_0_1.zip +0 -0
  155. /package/examples/{planning Poker → apps/planning Poker}/additional files/PokerTables_1_0_0_1.zip +0 -0
  156. /package/examples/{outlook Demo2 → apps/solution explorer}/dist/icon-512.png +0 -0
  157. /package/examples/{outlook Demo2 → apps/solution explorer}/dist/power-apps-data.js +0 -0
  158. /package/examples/{todo → apps/todo}/dist/icon192.png +0 -0
@@ -1,262 +1,899 @@
1
-
2
- import { getItemsByName, getItems, listTables } from './codeapp.js';
3
-
4
- let sSiteUrl = '<SITE_URL>';
5
- let sListName = '<LIST_NAME>';
6
- let iTopRows = 20;
7
-
8
- function getElementById(sId) {
9
- return document.getElementById(sId);
10
- }
11
-
12
- function setStatus(sMessage, bIsError = false) {
13
- let eStatusBar = getElementById('statusBar');
14
-
15
- if (!eStatusBar) {
16
- return;
17
- }
18
-
19
- eStatusBar.hidden = false;
20
- eStatusBar.textContent = sMessage;
21
- eStatusBar.dataset.state = bIsError ? 'error' : 'info';
22
- }
23
-
24
- function hideStatus() {
25
- let eStatusBar = getElementById('statusBar');
26
-
27
- if (!eStatusBar) {
28
- return;
29
- }
30
-
31
- eStatusBar.hidden = true;
32
- }
33
-
34
- function setMetaLabels(iRowCount) {
35
- let eSiteLabel = getElementById('siteLabel');
36
- let eListLabel = getElementById('listLabel');
37
- let eCountLabel = getElementById('countLabel');
38
-
39
- if (eSiteLabel) {
40
- eSiteLabel.textContent = 'Site: ' + sSiteUrl;
41
- }
42
-
43
- if (eListLabel) {
44
- eListLabel.textContent = 'List: ' + sListName;
45
- }
46
-
47
- if (eCountLabel) {
48
- eCountLabel.textContent = 'Rows: ' + iRowCount;
49
- }
50
- }
51
-
52
- function normalizeResultArray(oResult) {
53
- if (Array.isArray(oResult)) {
54
- return oResult;
55
- }
56
-
57
- if (oResult && Array.isArray(oResult.value)) {
58
- return oResult.value;
59
- }
60
-
61
- if (oResult && oResult.body && Array.isArray(oResult.body.value)) {
62
- return oResult.body.value;
63
- }
64
-
65
- if (oResult && Array.isArray(oResult.body)) {
66
- return oResult.body;
67
- }
68
-
69
- return [];
70
- }
71
-
72
- function isRenderableValue(value) {
73
- return value === null || ['string', 'number', 'boolean'].includes(typeof value);
74
- }
75
-
76
- function getVisibleColumns(aRows) {
77
- let aIgnoredColumns = [
78
- '@odata.etag',
79
- 'Attachments',
80
- 'ContentTypeId',
81
- 'GUID',
82
- 'ComplianceAssetId',
83
- 'odata.editLink'
84
- ];
85
-
86
- let aColumns = [];
87
-
88
- aRows.forEach((oRow) => {
89
- Object.keys(oRow || {}).forEach((sKey) => {
90
- let bIgnored =
91
- aIgnoredColumns.includes(sKey) ||
92
- sKey.indexOf('@odata') === 0 ||
93
- sKey.indexOf('_') === 0;
94
-
95
- if (bIgnored || aColumns.includes(sKey)) {
96
- return;
97
- }
98
-
99
- if (isRenderableValue(oRow[sKey])) {
100
- aColumns.push(sKey);
101
- }
102
- });
103
- });
104
-
105
- return aColumns;
106
- }
107
-
108
- function formatCellValue(value) {
109
- if (value === null || value === undefined || value === '') {
110
- return '\u2014';
111
- }
112
-
113
- if (typeof value === 'boolean') {
114
- return value ? 'Yes' : 'No';
115
- }
116
-
117
- return String(value);
118
- }
119
-
120
- function renderTable(aRows) {
121
- let eTable = getElementById('listTable');
122
- let eTableHead = getElementById('tableHead');
123
- let eTableBody = getElementById('tableBody');
124
- let eEmptyState = getElementById('emptyState');
125
-
126
- if (!eTable || !eTableHead || !eTableBody || !eEmptyState) {
127
- return;
128
- }
129
-
130
- eTableHead.innerHTML = '';
131
- eTableBody.innerHTML = '';
132
-
133
- if (!aRows.length) {
134
- eTable.hidden = true;
135
- eEmptyState.hidden = false;
136
- return;
137
- }
138
-
139
- let aColumns = getVisibleColumns(aRows);
140
-
141
- if (!aColumns.length) {
142
- eTable.hidden = true;
143
- eEmptyState.hidden = false;
144
- setStatus('Rows loaded, but no simple text columns were available to render.');
145
- return;
146
- }
147
-
148
- let eHeaderRow = document.createElement('tr');
149
-
150
- aColumns.forEach((sColumn) => {
151
- let eHeaderCell = document.createElement('th');
152
- eHeaderCell.textContent = sColumn;
153
- eHeaderRow.appendChild(eHeaderCell);
154
- });
155
-
156
- eTableHead.appendChild(eHeaderRow);
157
-
158
- aRows.forEach((oRow) => {
159
- let eBodyRow = document.createElement('tr');
160
-
161
- aColumns.forEach((sColumn) => {
162
- let eBodyCell = document.createElement('td');
163
- eBodyCell.textContent = formatCellValue(oRow[sColumn]);
164
- eBodyRow.appendChild(eBodyCell);
165
- });
166
-
167
- eTableBody.appendChild(eBodyRow);
168
- });
169
-
170
- eEmptyState.hidden = true;
171
- eTable.hidden = false;
172
- }
173
-
174
- function normalizeText(value) {
175
- return String(value || '').trim().toLowerCase();
176
- }
177
-
178
- function getListIdentifier(oList) {
179
- if (!oList) return '';
180
- return oList.Name || oList.name || oList.Id || oList.id || oList.TableName || oList.tableName || '';
181
- }
182
-
183
- function matchesListName(oList) {
184
- let sTarget = normalizeText(sListName);
185
- let aCandidates = [
186
- oList ? oList.DisplayName : null,
187
- oList ? oList.displayName : null,
188
- oList ? oList.Title : null,
189
- oList ? oList.title : null,
190
- oList ? oList.Name : null,
191
- oList ? oList.name : null
192
- ]
193
- .filter(Boolean)
194
- .map((value) => normalizeText(value));
195
-
196
- return aCandidates.includes(sTarget);
197
- }
198
-
199
- async function loadRowsByListName() {
200
- let oResult = await getItemsByName(sSiteUrl, sListName, { top: iTopRows });
201
- return normalizeResultArray(oResult).slice(0, iTopRows);
202
- }
203
-
204
- async function loadRowsByResolvedListId() {
205
- setStatus('List name lookup failed. Trying connector table lookup...');
206
-
207
- let oTablesResult = await listTables(sSiteUrl);
208
- let aLists = normalizeResultArray(oTablesResult);
209
- let oMatchedList = aLists.find((oList) => matchesListName(oList));
210
- let sResolvedListId = getListIdentifier(oMatchedList);
211
-
212
- if (!sResolvedListId) {
213
- throw new Error('List \'' + sListName + '\' was not found in connector metadata.');
214
- }
215
-
216
- let oItemsResult = await getItems(sSiteUrl, sResolvedListId, { top: iTopRows });
217
- return normalizeResultArray(oItemsResult).slice(0, iTopRows);
218
- }
219
-
220
- function shouldTryFallback(oError) {
221
- let sMessage = oError && oError.message ? oError.message : String(oError);
222
- return sMessage.indexOf('404') >= 0 || sMessage.indexOf('Resource not found') >= 0;
223
- }
224
-
225
- async function loadSharePointRows() {
226
- setStatus('Loading rows from SharePoint...');
227
-
228
- let aRows = [];
229
-
230
- try {
231
- aRows = await loadRowsByListName();
232
- } catch (oError) {
233
- if (!shouldTryFallback(oError)) {
234
- throw oError;
235
- }
236
-
237
- aRows = await loadRowsByResolvedListId();
238
- }
239
-
240
- setMetaLabels(aRows.length);
241
- renderTable(aRows);
242
-
243
- if (aRows.length) {
244
- hideStatus();
245
- return;
246
- }
247
-
248
- setStatus('The list loaded successfully, but no rows were returned.');
249
- }
250
-
251
- async function boot() {
252
- setMetaLabels(0);
253
-
254
- try {
255
- await loadSharePointRows();
256
- } catch (oErr) {
257
- let sMessage = oErr && oErr.message ? oErr.message : String(oErr);
258
- setStatus('SharePoint: ' + sMessage, true);
259
- }
260
- }
261
-
262
- boot();
1
+ import { enableDebugger } from "./codeapp.js";
2
+
3
+ enableDebugger();
4
+
5
+ import {
6
+ createSpItem,
7
+ deleteSpItem,
8
+ getItems,
9
+ listTables,
10
+ updateSpItem,
11
+ } from './sharepoint.js';
12
+
13
+ const oAppConfig = {
14
+ sAppName: 'SharePoint Demo App',
15
+ sSiteUrl: 'https://37wcqv.sharepoint.com/sites/testsite',
16
+ sListName: 'Test List',
17
+ };
18
+
19
+ const aSystemFieldNames = [
20
+ 'ID',
21
+ 'Id',
22
+ 'GUID',
23
+ 'Modified',
24
+ 'Created',
25
+ 'Author',
26
+ 'AuthorId',
27
+ 'Editor',
28
+ 'EditorId',
29
+ 'Attachments',
30
+ 'FileRef',
31
+ 'FileLeafRef',
32
+ 'ContentType',
33
+ 'ContentTypeId',
34
+ 'ComplianceAssetId',
35
+ 'FolderChildCount',
36
+ 'ItemChildCount',
37
+ '_UIVersionString',
38
+ '_ModerationStatus',
39
+ '_ModerationComments',
40
+ 'AppAuthor',
41
+ 'AppEditor',
42
+ 'LinkTitleNoMenu',
43
+ 'LinkTitle',
44
+ 'Edit',
45
+ 'DocIcon',
46
+ 'Order',
47
+ 'SortBehavior',
48
+ 'WorkflowVersion',
49
+ ];
50
+
51
+ const oState = {
52
+ eRoot: null,
53
+ oListAccess: null,
54
+ aItems: [],
55
+ aFields: [],
56
+ sError: '',
57
+ sNotice: '',
58
+ bLoading: true,
59
+ bRefreshing: false,
60
+ bSubmittingCreate: false,
61
+ bSubmittingEdit: false,
62
+ iSelectedItemId: null,
63
+ };
64
+
65
+ async function boot() {
66
+ oState.eRoot = document.getElementById('root');
67
+ if (!oState.eRoot) {
68
+ throw new Error('Root element not found.');
69
+ }
70
+
71
+ oState.eRoot.addEventListener('click', handleRootClick);
72
+ oState.eRoot.addEventListener('submit', handleRootSubmit);
73
+
74
+ renderApp();
75
+ await initializeApp();
76
+ }
77
+
78
+ async function initializeApp() {
79
+ try {
80
+ oState.oListAccess = await resolveListAccess();
81
+ await refreshAppData({ bPreserveSelection: false });
82
+ oState.sNotice = 'Connected to ' + oState.oListAccess.sListName + ' and ready for CRUD operations.';
83
+ } catch (oError) {
84
+ oState.sError = getErrorMessage(oError);
85
+ } finally {
86
+ oState.bLoading = false;
87
+ renderApp();
88
+ }
89
+ }
90
+
91
+ async function resolveListAccess() {
92
+ try {
93
+ const oTableAccess = await resolveListAccessFromTables();
94
+ if (oTableAccess) {
95
+ return oTableAccess;
96
+ }
97
+ } catch (oError) {
98
+ throw new Error('SharePoint table lookup failed: ' + getErrorMessage(oError));
99
+ }
100
+
101
+ throw new Error('The SharePoint list "' + oAppConfig.sListName + '" could not be resolved through connector table lookup. Configure a real SharePoint list ID or expose a matching list through listTables.');
102
+ }
103
+
104
+ async function resolveListAccessFromTables() {
105
+ const oTablesResponse = await listTables(oAppConfig.sSiteUrl);
106
+ const aTables = normalizeCollection(oTablesResponse);
107
+ const sTargetName = normalizeString(oAppConfig.sListName);
108
+ const oMatchedTable = aTables.find(function(oTable) {
109
+ return getListTableNames(oTable).some(function(sCandidate) {
110
+ return normalizeString(sCandidate) === sTargetName;
111
+ });
112
+ });
113
+
114
+ if (!oMatchedTable) {
115
+ return null;
116
+ }
117
+
118
+ const sListId = getTableId(oMatchedTable);
119
+ if (!sListId) {
120
+ throw new Error('The SharePoint connector returned the list but did not expose a usable list identifier.');
121
+ }
122
+
123
+ return {
124
+ sSiteUrl: oAppConfig.sSiteUrl,
125
+ sListName: getPreferredListName(oMatchedTable),
126
+ sListId: sListId,
127
+ sAccessLabel: 'Connector table API',
128
+ };
129
+ }
130
+
131
+ async function refreshAppData({ bPreserveSelection } = { bPreserveSelection: true }) {
132
+ const aItems = await fetchItems();
133
+ const aFields = await loadFieldDefinitions(aItems);
134
+ oState.aItems = sortItemsDescending(aItems);
135
+ oState.aFields = aFields;
136
+
137
+ const iExistingSelection = bPreserveSelection ? oState.iSelectedItemId : null;
138
+ const bSelectionStillExists = iExistingSelection != null && oState.aItems.some(function(oItem) {
139
+ return getItemId(oItem) === iExistingSelection;
140
+ });
141
+
142
+ oState.iSelectedItemId = bSelectionStillExists ? iExistingSelection : (oState.aItems[0] ? getItemId(oState.aItems[0]) : null);
143
+ }
144
+
145
+ async function fetchItems() {
146
+ const oResponse = await getItems(oState.oListAccess.sSiteUrl, oState.oListAccess.sListId, { top: 200 });
147
+ return normalizeCollection(oResponse);
148
+ }
149
+
150
+ async function loadFieldDefinitions(aItems) {
151
+ return ensureTitleField(sortFields(deriveFieldsFromItems(aItems)));
152
+ }
153
+
154
+ function deriveFieldsFromItems(aItems) {
155
+ const oFieldMap = new Map();
156
+
157
+ aItems.forEach(function(oItem) {
158
+ Object.entries(oItem || {}).forEach(function([sName, value]) {
159
+ if (isSystemField(sName) || !isEditablePrimitive(value)) {
160
+ return;
161
+ }
162
+
163
+ if (!oFieldMap.has(sName)) {
164
+ oFieldMap.set(sName, {
165
+ sName: sName,
166
+ sLabel: toLabel(sName),
167
+ sType: inferFieldTypeFromValue(value),
168
+ bRequired: sName === 'Title',
169
+ aChoices: [],
170
+ sDefaultValue: '',
171
+ });
172
+ }
173
+ });
174
+ });
175
+
176
+ return Array.from(oFieldMap.values());
177
+ }
178
+
179
+ function ensureTitleField(aFields) {
180
+ const bHasTitle = aFields.some(function(oField) {
181
+ return oField.sName === 'Title';
182
+ });
183
+
184
+ if (!bHasTitle) {
185
+ aFields.unshift({
186
+ sName: 'Title',
187
+ sLabel: 'Title',
188
+ sType: 'Text',
189
+ bRequired: true,
190
+ aChoices: [],
191
+ sDefaultValue: '',
192
+ });
193
+ }
194
+
195
+ return aFields;
196
+ }
197
+
198
+ function sortFields(aFields) {
199
+ return aFields.slice().sort(function(oLeft, oRight) {
200
+ if (oLeft.sName === 'Title') return -1;
201
+ if (oRight.sName === 'Title') return 1;
202
+ if (oLeft.bRequired !== oRight.bRequired) return oLeft.bRequired ? -1 : 1;
203
+ return oLeft.sLabel.localeCompare(oRight.sLabel);
204
+ });
205
+ }
206
+
207
+ function renderApp() {
208
+ const oSelectedItem = getSelectedItem();
209
+ const aPreviewFields = getPreviewFields();
210
+
211
+ oState.eRoot.innerHTML = `
212
+ <main class="app-shell">
213
+ <section class="hero">
214
+ <div>
215
+ <p class="eyebrow">SharePoint Integration Demo</p>
216
+ <h1>${escapeHtml(oAppConfig.sAppName)}</h1>
217
+ <p>Browse live list items, add a new record, edit the selected entry, and delete records without leaving the page. The form adapts to your list fields when SharePoint metadata is available.</p>
218
+ ${renderStatusMarkup()}
219
+ <div class="pill-row">
220
+ <span class="pill"><b>Mode</b>${escapeHtml(oState.oListAccess ? oState.oListAccess.sAccessLabel : 'Connecting')}</span>
221
+ <span class="pill"><b>List</b>${escapeHtml(oState.oListAccess ? oState.oListAccess.sListName : oAppConfig.sListName)}</span>
222
+ <span class="pill"><b>Editable Fields</b>${String(oState.aFields.length)}</span>
223
+ </div>
224
+ </div>
225
+ <div class="hero-meta">
226
+ <div class="meta-card">
227
+ <span class="meta-label">SharePoint Site</span>
228
+ <span class="meta-value mono">${escapeHtml(oAppConfig.sSiteUrl)}</span>
229
+ </div>
230
+ <div class="meta-card">
231
+ <span class="meta-label">Configured List</span>
232
+ <span class="meta-value">${escapeHtml(oAppConfig.sListName)}</span>
233
+ </div>
234
+ <div class="meta-card">
235
+ <span class="meta-label">Demo Focus</span>
236
+ <span class="meta-value">List items, connector table discovery, and table-ID CRUD forms.</span>
237
+ </div>
238
+ </div>
239
+ </section>
240
+
241
+ <section class="metrics">
242
+ <article class="metric">
243
+ <span class="metric-label">Total Items</span>
244
+ <span class="metric-value">${String(oState.aItems.length)}</span>
245
+ </article>
246
+ <article class="metric">
247
+ <span class="metric-label">Selected Item</span>
248
+ <span class="metric-value">${oSelectedItem ? String(getItemId(oSelectedItem)) : '0'}</span>
249
+ </article>
250
+ <article class="metric">
251
+ <span class="metric-label">Connection State</span>
252
+ <span class="metric-value">${oState.bLoading ? '...' : 'Live'}</span>
253
+ </article>
254
+ </section>
255
+
256
+ <section class="panel-grid">
257
+ <article class="panel">
258
+ <div class="panel-header">
259
+ <div>
260
+ <h2>Add Record</h2>
261
+ <p>Create a new SharePoint item using discovered fields. Required columns are marked automatically when metadata is available.</p>
262
+ </div>
263
+ </div>
264
+ ${renderCreateForm()}
265
+ </article>
266
+
267
+ <section>
268
+ <article class="panel">
269
+ <div class="panel-header">
270
+ <div>
271
+ <h2>List Items</h2>
272
+ <p>All fetched records are shown below. Select a row to edit it or delete it directly from the table.</p>
273
+ </div>
274
+ <div class="button-row">
275
+ <button class="button-ghost" type="button" data-action="refresh" ${isActionDisabled() ? 'disabled' : ''}>${oState.bRefreshing ? 'Refreshing...' : 'Refresh list'}</button>
276
+ </div>
277
+ </div>
278
+ ${renderTableMarkup(aPreviewFields)}
279
+ </article>
280
+
281
+ <article class="editor-panel">
282
+ ${renderEditorMarkup(oSelectedItem)}
283
+ </article>
284
+ </section>
285
+ </section>
286
+ </main>
287
+ `;
288
+ }
289
+
290
+ function renderStatusMarkup() {
291
+ if (!oState.sError && !oState.sNotice && !oState.bLoading) {
292
+ return '';
293
+ }
294
+
295
+ if (oState.sError) {
296
+ return `
297
+ <div class="status" data-tone="error">
298
+ <div>!</div>
299
+ <div>
300
+ <strong>Connection or operation error</strong>
301
+ <div>${escapeHtml(oState.sError)}</div>
302
+ </div>
303
+ </div>
304
+ `;
305
+ }
306
+
307
+ return `
308
+ <div class="status" data-tone="success">
309
+ <div>${oState.bLoading ? '...' : 'OK'}</div>
310
+ <div>
311
+ <strong>${oState.bLoading ? 'Connecting to SharePoint' : 'Ready'}</strong>
312
+ <div>${escapeHtml(oState.bLoading ? 'Loading list configuration and items.' : oState.sNotice)}</div>
313
+ </div>
314
+ </div>
315
+ `;
316
+ }
317
+
318
+ function renderCreateForm() {
319
+ return `
320
+ <form id="create-form" class="field-grid">
321
+ ${renderFieldInputs(null, 'create')}
322
+ <p class="helper-text">Use advanced payload overrides if your list has additional complex columns you want to send manually.</p>
323
+ <details class="details-box">
324
+ <summary>Advanced payload overrides</summary>
325
+ <div class="details-inner">
326
+ <div class="field">
327
+ <label for="create-overrides">JSON object merged into the create request</label>
328
+ <textarea id="create-overrides" name="payloadOverrides">{}</textarea>
329
+ </div>
330
+ </div>
331
+ </details>
332
+ <div class="button-row">
333
+ <button class="button" type="submit" ${isActionDisabled() ? 'disabled' : ''}>${oState.bSubmittingCreate ? 'Adding...' : 'Add item'}</button>
334
+ <button class="button-ghost" type="reset" data-action="refresh" ${isActionDisabled() ? 'disabled' : ''}>Reload defaults</button>
335
+ </div>
336
+ </form>
337
+ `;
338
+ }
339
+
340
+ function renderTableMarkup(aPreviewFields) {
341
+ if (oState.bLoading) {
342
+ return '<div class="empty-state">Loading list items from SharePoint.</div>';
343
+ }
344
+
345
+ if (oState.aItems.length === 0) {
346
+ return '<div class="empty-state">No items were returned from the list yet. Use the create form to add the first record.</div>';
347
+ }
348
+
349
+ const sHeaderCells = aPreviewFields.map(function(oField) {
350
+ return '<th scope="col">' + escapeHtml(oField.sLabel) + '</th>';
351
+ }).join('');
352
+
353
+ const sRows = oState.aItems.map(function(oItem) {
354
+ const iItemId = getItemId(oItem);
355
+ const bIsSelected = iItemId === oState.iSelectedItemId;
356
+ const sTitle = getPrimaryTitle(oItem);
357
+ const sPreviewCells = aPreviewFields.map(function(oField) {
358
+ return '<td data-label="' + escapeHtml(oField.sLabel) + '">' + escapeHtml(formatPreviewValue(oItem[oField.sName])) + '</td>';
359
+ }).join('');
360
+
361
+ return `
362
+ <tr class="item-row ${bIsSelected ? 'is-selected' : ''}" data-select-item="${String(iItemId)}">
363
+ <td data-label="Record">
364
+ <span class="row-title">${escapeHtml(sTitle)}</span>
365
+ <span class="row-subtitle">ID ${String(iItemId)}</span>
366
+ </td>
367
+ ${sPreviewCells}
368
+ <td data-label="Actions">
369
+ <div class="table-actions">
370
+ <button class="button-ghost" type="button" data-select-item="${String(iItemId)}">Edit</button>
371
+ <button class="button-danger" type="button" data-delete-item="${String(iItemId)}" ${isActionDisabled() ? 'disabled' : ''}>Delete</button>
372
+ </div>
373
+ </td>
374
+ </tr>
375
+ `;
376
+ }).join('');
377
+
378
+ return `
379
+ <div class="table-wrap">
380
+ <table class="item-table">
381
+ <thead>
382
+ <tr>
383
+ <th scope="col">Record</th>
384
+ ${sHeaderCells}
385
+ <th scope="col">Actions</th>
386
+ </tr>
387
+ </thead>
388
+ <tbody>${sRows}</tbody>
389
+ </table>
390
+ </div>
391
+ `;
392
+ }
393
+
394
+ function renderEditorMarkup(oSelectedItem) {
395
+ if (!oSelectedItem) {
396
+ return `
397
+ <div class="editor-empty">
398
+ Select a record from the table to inspect and update its fields. Delete is also available here for the active selection.
399
+ </div>
400
+ `;
401
+ }
402
+
403
+ return `
404
+ <div class="editor-head">
405
+ <div>
406
+ <span class="tag">Selected record</span>
407
+ <h2>${escapeHtml(getPrimaryTitle(oSelectedItem))}</h2>
408
+ <p>Edit the fields below, then save the record back to SharePoint.</p>
409
+ </div>
410
+ <div class="muted">Item ID ${String(getItemId(oSelectedItem))}</div>
411
+ </div>
412
+ <form id="edit-form" class="field-grid" data-item-id="${String(getItemId(oSelectedItem))}">
413
+ ${renderFieldInputs(oSelectedItem, 'edit')}
414
+ <details class="details-box">
415
+ <summary>Advanced payload overrides</summary>
416
+ <div class="details-inner">
417
+ <div class="field">
418
+ <label for="edit-overrides">JSON object merged into the update request</label>
419
+ <textarea id="edit-overrides" name="payloadOverrides">{}</textarea>
420
+ </div>
421
+ <p class="helper-text">Current item snapshot:</p>
422
+ <div class="field">
423
+ <textarea readonly>${escapeHtml(JSON.stringify(oSelectedItem, null, 2))}</textarea>
424
+ </div>
425
+ </div>
426
+ </details>
427
+ <div class="button-row">
428
+ <button class="button" type="submit" ${isActionDisabled() ? 'disabled' : ''}>${oState.bSubmittingEdit ? 'Saving...' : 'Save changes'}</button>
429
+ <button class="button-danger" type="button" data-delete-item="${String(getItemId(oSelectedItem))}" ${isActionDisabled() ? 'disabled' : ''}>Delete item</button>
430
+ </div>
431
+ </form>
432
+ `;
433
+ }
434
+
435
+ function renderFieldInputs(oItem, sFormMode) {
436
+ return oState.aFields.map(function(oField) {
437
+ const value = oItem ? oItem[oField.sName] : (oField.sDefaultValue || '');
438
+ return renderFieldMarkup(oField, value, sFormMode);
439
+ }).join('');
440
+ }
441
+
442
+ function renderFieldMarkup(oField, value, sFormMode) {
443
+ const sFieldName = 'field:' + oField.sName;
444
+ const sId = sFormMode + '-' + oField.sName;
445
+ const sRequired = oField.bRequired ? 'required' : '';
446
+ const sLabel = escapeHtml(oField.sLabel);
447
+
448
+ if (oField.sType === 'Boolean') {
449
+ return `
450
+ <div class="field">
451
+ <label for="${escapeHtml(sId)}"><span>${sLabel}${oField.bRequired ? '<em>required</em>' : ''}</span></label>
452
+ <label class="checkbox-field" for="${escapeHtml(sId)}">
453
+ <input id="${escapeHtml(sId)}" name="${escapeHtml(sFieldName)}" type="checkbox" ${value ? 'checked' : ''} />
454
+ <span>${value ? 'Enabled' : 'Disabled'}</span>
455
+ </label>
456
+ </div>
457
+ `;
458
+ }
459
+
460
+ if (oField.sType === 'Choice' && oField.aChoices.length > 0) {
461
+ const sOptions = ['<option value="">Select a value</option>'].concat(oField.aChoices.map(function(sChoice) {
462
+ const bSelected = String(value || '') === String(sChoice);
463
+ return '<option value="' + escapeHtml(String(sChoice)) + '" ' + (bSelected ? 'selected' : '') + '>' + escapeHtml(String(sChoice)) + '</option>';
464
+ })).join('');
465
+
466
+ return `
467
+ <div class="field">
468
+ <label for="${escapeHtml(sId)}"><span>${sLabel}${oField.bRequired ? '<em>required</em>' : ''}</span></label>
469
+ <select id="${escapeHtml(sId)}" name="${escapeHtml(sFieldName)}" ${sRequired}>${sOptions}</select>
470
+ </div>
471
+ `;
472
+ }
473
+
474
+ if (oField.sType === 'Note') {
475
+ return `
476
+ <div class="field">
477
+ <label for="${escapeHtml(sId)}"><span>${sLabel}${oField.bRequired ? '<em>required</em>' : ''}</span></label>
478
+ <textarea id="${escapeHtml(sId)}" name="${escapeHtml(sFieldName)}" ${sRequired}>${escapeHtml(formatInputValue(oField, value))}</textarea>
479
+ </div>
480
+ `;
481
+ }
482
+
483
+ const sType = oField.sType === 'Number' ? 'number' : (oField.sType === 'DateTime' ? 'datetime-local' : 'text');
484
+ const sStep = oField.sType === 'Number' ? 'step="any"' : '';
485
+
486
+ return `
487
+ <div class="field">
488
+ <label for="${escapeHtml(sId)}"><span>${sLabel}${oField.bRequired ? '<em>required</em>' : ''}</span></label>
489
+ <input id="${escapeHtml(sId)}" name="${escapeHtml(sFieldName)}" type="${sType}" value="${escapeAttribute(formatInputValue(oField, value))}" ${sRequired} ${sStep} />
490
+ </div>
491
+ `;
492
+ }
493
+
494
+ async function handleRootClick(oEvent) {
495
+ const eAction = oEvent.target.closest('[data-action], [data-select-item], [data-delete-item]');
496
+ if (!eAction) {
497
+ return;
498
+ }
499
+
500
+ if (eAction.hasAttribute('data-select-item')) {
501
+ const iItemId = Number(eAction.getAttribute('data-select-item'));
502
+ if (!Number.isNaN(iItemId)) {
503
+ oState.iSelectedItemId = iItemId;
504
+ oState.sError = '';
505
+ oState.sNotice = 'Editing item ' + String(iItemId) + '.';
506
+ renderApp();
507
+ }
508
+ return;
509
+ }
510
+
511
+ if (eAction.hasAttribute('data-delete-item')) {
512
+ const iItemId = Number(eAction.getAttribute('data-delete-item'));
513
+ if (!Number.isNaN(iItemId)) {
514
+ await handleDelete(iItemId);
515
+ }
516
+ return;
517
+ }
518
+
519
+ const sAction = eAction.getAttribute('data-action');
520
+ if (sAction === 'refresh') {
521
+ await handleRefresh();
522
+ }
523
+ }
524
+
525
+ async function handleRootSubmit(oEvent) {
526
+ oEvent.preventDefault();
527
+ const eForm = oEvent.target;
528
+ if (!(eForm instanceof HTMLFormElement)) {
529
+ return;
530
+ }
531
+
532
+ if (eForm.id === 'create-form') {
533
+ await handleCreateSubmit(eForm);
534
+ return;
535
+ }
536
+
537
+ if (eForm.id === 'edit-form') {
538
+ await handleEditSubmit(eForm);
539
+ }
540
+ }
541
+
542
+ async function handleRefresh() {
543
+ oState.bRefreshing = true;
544
+ oState.sError = '';
545
+ oState.sNotice = 'Refreshing items from SharePoint.';
546
+ renderApp();
547
+
548
+ try {
549
+ await refreshAppData({ bPreserveSelection: true });
550
+ oState.sNotice = 'List refreshed successfully.';
551
+ } catch (oError) {
552
+ oState.sError = getErrorMessage(oError);
553
+ } finally {
554
+ oState.bRefreshing = false;
555
+ renderApp();
556
+ }
557
+ }
558
+
559
+ async function handleCreateSubmit(eForm) {
560
+ let oPayload;
561
+ try {
562
+ oPayload = buildPayloadFromForm(eForm);
563
+ } catch (oError) {
564
+ oState.sError = getErrorMessage(oError);
565
+ renderApp();
566
+ return;
567
+ }
568
+
569
+ oState.bSubmittingCreate = true;
570
+ oState.sError = '';
571
+ oState.sNotice = 'Creating a new SharePoint item.';
572
+ renderApp();
573
+
574
+ try {
575
+ const oResult = await createListItem(oPayload);
576
+ await refreshAppData({ bPreserveSelection: false });
577
+ const iCreatedItemId = getItemId(oResult);
578
+ if (iCreatedItemId != null) {
579
+ oState.iSelectedItemId = iCreatedItemId;
580
+ }
581
+ oState.sNotice = 'New item created successfully.';
582
+ } catch (oError) {
583
+ oState.sError = getErrorMessage(oError);
584
+ } finally {
585
+ oState.bSubmittingCreate = false;
586
+ renderApp();
587
+ }
588
+ }
589
+
590
+ async function handleEditSubmit(eForm) {
591
+ const iItemId = Number(eForm.getAttribute('data-item-id'));
592
+ let oPayload;
593
+ try {
594
+ oPayload = buildPayloadFromForm(eForm);
595
+ } catch (oError) {
596
+ oState.sError = getErrorMessage(oError);
597
+ renderApp();
598
+ return;
599
+ }
600
+
601
+ oState.bSubmittingEdit = true;
602
+ oState.sError = '';
603
+ oState.sNotice = 'Saving changes to item ' + String(iItemId) + '.';
604
+ renderApp();
605
+
606
+ try {
607
+ await updateListItem(iItemId, oPayload);
608
+ await refreshAppData({ bPreserveSelection: true });
609
+ oState.sNotice = 'Item ' + String(iItemId) + ' updated successfully.';
610
+ } catch (oError) {
611
+ oState.sError = getErrorMessage(oError);
612
+ } finally {
613
+ oState.bSubmittingEdit = false;
614
+ renderApp();
615
+ }
616
+ }
617
+
618
+ async function handleDelete(iItemId) {
619
+ if (!window.confirm('Delete SharePoint item ' + String(iItemId) + '?')) {
620
+ return;
621
+ }
622
+
623
+ oState.bSubmittingEdit = true;
624
+ oState.sError = '';
625
+ oState.sNotice = 'Deleting item ' + String(iItemId) + '.';
626
+ renderApp();
627
+
628
+ try {
629
+ await deleteListItem(iItemId);
630
+ await refreshAppData({ bPreserveSelection: false });
631
+ oState.sNotice = 'Item ' + String(iItemId) + ' deleted successfully.';
632
+ } catch (oError) {
633
+ oState.sError = getErrorMessage(oError);
634
+ } finally {
635
+ oState.bSubmittingEdit = false;
636
+ renderApp();
637
+ }
638
+ }
639
+
640
+ async function createListItem(oPayload) {
641
+ return createSpItem(oState.oListAccess.sSiteUrl, oState.oListAccess.sListId, oPayload);
642
+ }
643
+
644
+ async function updateListItem(iItemId, oPayload) {
645
+ return updateSpItem(oState.oListAccess.sSiteUrl, oState.oListAccess.sListId, iItemId, oPayload);
646
+ }
647
+
648
+ async function deleteListItem(iItemId) {
649
+ return deleteSpItem(oState.oListAccess.sSiteUrl, oState.oListAccess.sListId, iItemId);
650
+ }
651
+
652
+ function buildPayloadFromForm(eForm) {
653
+ const oPayload = {};
654
+
655
+ oState.aFields.forEach(function(oField) {
656
+ const eField = eForm.elements.namedItem('field:' + oField.sName);
657
+ if (!eField) {
658
+ return;
659
+ }
660
+ oPayload[oField.sName] = readFieldValue(oField, eField);
661
+ });
662
+
663
+ const eOverridesField = eForm.elements.namedItem('payloadOverrides');
664
+ const sOverrides = eOverridesField && 'value' in eOverridesField ? String(eOverridesField.value || '').trim() : '';
665
+ if (sOverrides) {
666
+ const oOverrides = JSON.parse(sOverrides);
667
+ if (!oOverrides || Array.isArray(oOverrides) || typeof oOverrides !== 'object') {
668
+ throw new Error('Advanced payload overrides must be a JSON object.');
669
+ }
670
+ return Object.assign(oPayload, oOverrides);
671
+ }
672
+
673
+ return oPayload;
674
+ }
675
+
676
+ function readFieldValue(oField, eField) {
677
+ if (oField.sType === 'Boolean' && eField instanceof HTMLInputElement) {
678
+ return eField.checked;
679
+ }
680
+
681
+ const sValue = 'value' in eField ? String(eField.value || '') : '';
682
+ if (oField.sType === 'Number') {
683
+ return sValue === '' ? null : Number(sValue);
684
+ }
685
+ if (oField.sType === 'DateTime') {
686
+ return sValue === '' ? null : new Date(sValue).toISOString();
687
+ }
688
+ return sValue;
689
+ }
690
+
691
+ function getSelectedItem() {
692
+ return oState.aItems.find(function(oItem) {
693
+ return getItemId(oItem) === oState.iSelectedItemId;
694
+ }) || null;
695
+ }
696
+
697
+ function getPreviewFields() {
698
+ return oState.aFields.filter(function(oField) {
699
+ return oField.sName !== 'Title';
700
+ }).slice(0, 3);
701
+ }
702
+
703
+ function normalizeCollection(oPayload) {
704
+ if (oPayload && typeof oPayload === 'object') {
705
+ const aNestedCandidates = [
706
+ oPayload.value,
707
+ oPayload.items,
708
+ oPayload.results,
709
+ oPayload.body,
710
+ oPayload.data,
711
+ oPayload.result,
712
+ oPayload.d,
713
+ oPayload.response,
714
+ ];
715
+
716
+ for (const oCandidate of aNestedCandidates) {
717
+ const aNormalizedCandidate = normalizeCollectionCandidate(oCandidate);
718
+ if (aNormalizedCandidate) {
719
+ return aNormalizedCandidate;
720
+ }
721
+ }
722
+ }
723
+
724
+ return normalizeCollectionCandidate(oPayload) || [];
725
+ }
726
+
727
+ function normalizeCollectionCandidate(oPayload) {
728
+ if (Array.isArray(oPayload)) {
729
+ return oPayload;
730
+ }
731
+ if (oPayload && Array.isArray(oPayload.value)) {
732
+ return oPayload.value;
733
+ }
734
+ if (oPayload && oPayload.d && Array.isArray(oPayload.d.results)) {
735
+ return oPayload.d.results;
736
+ }
737
+ if (oPayload && Array.isArray(oPayload.items)) {
738
+ return oPayload.items;
739
+ }
740
+ if (oPayload && Array.isArray(oPayload.results)) {
741
+ return oPayload.results;
742
+ }
743
+ return null;
744
+ }
745
+
746
+ function sortItemsDescending(aItems) {
747
+ return aItems.slice().sort(function(oLeft, oRight) {
748
+ return Number(getItemId(oRight) || 0) - Number(getItemId(oLeft) || 0);
749
+ });
750
+ }
751
+
752
+ function getItemId(oItem) {
753
+ if (!oItem || typeof oItem !== 'object') {
754
+ return null;
755
+ }
756
+ return oItem.ID ?? oItem.Id ?? oItem.id ?? null;
757
+ }
758
+
759
+ function getPrimaryTitle(oItem) {
760
+ return String(oItem.Title || oItem.title || 'Untitled item');
761
+ }
762
+
763
+ function formatPreviewValue(value) {
764
+ if (value == null || value === '') {
765
+ return '—';
766
+ }
767
+ if (typeof value === 'boolean') {
768
+ return value ? 'Yes' : 'No';
769
+ }
770
+ if (typeof value === 'object') {
771
+ return '[complex value]';
772
+ }
773
+ return String(value);
774
+ }
775
+
776
+ function formatInputValue(oField, value) {
777
+ if (value == null) {
778
+ return '';
779
+ }
780
+ if (oField.sType === 'DateTime') {
781
+ const oDate = new Date(value);
782
+ if (Number.isNaN(oDate.getTime())) {
783
+ return '';
784
+ }
785
+ const iTimezoneOffset = oDate.getTimezoneOffset();
786
+ const oLocalDate = new Date(oDate.getTime() - (iTimezoneOffset * 60000));
787
+ return oLocalDate.toISOString().slice(0, 16);
788
+ }
789
+ return String(value);
790
+ }
791
+
792
+ function normalizeChoices(choices) {
793
+ if (Array.isArray(choices)) {
794
+ return choices;
795
+ }
796
+ if (choices && Array.isArray(choices.results)) {
797
+ return choices.results;
798
+ }
799
+ return [];
800
+ }
801
+
802
+ function inferFieldTypeFromValue(value) {
803
+ if (typeof value === 'boolean') return 'Boolean';
804
+ if (typeof value === 'number') return 'Number';
805
+ if (typeof value === 'string' && value.length > 80) return 'Note';
806
+ if (typeof value === 'string' && !Number.isNaN(new Date(value).getTime()) && value.includes('T')) return 'DateTime';
807
+ return 'Text';
808
+ }
809
+
810
+ function isEditablePrimitive(value) {
811
+ return value == null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
812
+ }
813
+
814
+ function isSystemField(sFieldName) {
815
+ return aSystemFieldNames.includes(sFieldName);
816
+ }
817
+
818
+ function getTableId(oTable) {
819
+ return oTable.Id
820
+ || oTable.id
821
+ || oTable.TableId
822
+ || oTable.tableId
823
+ || oTable.TableName
824
+ || oTable.tableName
825
+ || oTable.EntitySetName
826
+ || oTable.entitySetName
827
+ || oTable.Name
828
+ || oTable.name
829
+ || null;
830
+ }
831
+
832
+ function getPreferredListName(oTable) {
833
+ return oTable.DisplayName || oTable.displayName || oTable.Title || oTable.title || oAppConfig.sListName;
834
+ }
835
+
836
+ function getListTableNames(oTable) {
837
+ return [
838
+ oTable.DisplayName,
839
+ oTable.displayName,
840
+ oTable.Title,
841
+ oTable.title,
842
+ oTable.TableName,
843
+ oTable.tableName,
844
+ oTable.Name,
845
+ oTable.name,
846
+ oTable.EntitySetName,
847
+ oTable.entitySetName,
848
+ ].filter(Boolean);
849
+ }
850
+
851
+ function normalizeString(sValue) {
852
+ return String(sValue || '').trim().toLowerCase();
853
+ }
854
+
855
+ function getErrorMessage(oError) {
856
+ if (!oError) {
857
+ return 'Unknown error';
858
+ }
859
+ if (typeof oError === 'string') {
860
+ return oError;
861
+ }
862
+ if (typeof oError.message === 'string' && oError.message) {
863
+ return oError.message;
864
+ }
865
+ try {
866
+ return JSON.stringify(oError);
867
+ } catch (oStringifyError) {
868
+ return String(oError);
869
+ }
870
+ }
871
+
872
+ function toLabel(sFieldName) {
873
+ return String(sFieldName)
874
+ .replace(new RegExp('_x0020_', 'g'), ' ')
875
+ .replace(new RegExp('([a-z])([A-Z])', 'g'), '$1 $2');
876
+ }
877
+
878
+ function escapeHtml(sValue) {
879
+ return String(sValue)
880
+ .replace(new RegExp('&', 'g'), '&amp;')
881
+ .replace(new RegExp('<', 'g'), '&lt;')
882
+ .replace(new RegExp('>', 'g'), '&gt;')
883
+ .replace(new RegExp('"', 'g'), '&quot;');
884
+ }
885
+
886
+ function escapeAttribute(sValue) {
887
+ return escapeHtml(sValue).replace(new RegExp("'", 'g'), '&#39;');
888
+ }
889
+
890
+ function isActionDisabled() {
891
+ return oState.bLoading || oState.bRefreshing || oState.bSubmittingCreate || oState.bSubmittingEdit;
892
+ }
893
+
894
+ boot().catch(function(oError) {
895
+ console.error(oError);
896
+ oState.sError = getErrorMessage(oError);
897
+ oState.bLoading = false;
898
+ renderApp();
899
+ });