codeapp-js 0.2.2 → 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 (176) 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/codeApp/.power/schemas/appschemas/dataSourcesInfo.ts +6275 -0
  15. package/codeApp/.power/schemas/jira/jira.Schema.json +6903 -0
  16. package/codeApp/.power/schemas/keyvault/keyvault.Schema.json +1600 -0
  17. package/{examples/combined demo/.power/schemas/office365groups/office365groups.Schema.json → codeApp/.power/schemas/office365groups/office365groups.Schema.json} +2203 -2203
  18. package/codeApp/.power/schemas/teams/teams.Schema.json +11112 -0
  19. package/codeApp/dist/codeapp.js +103 -1043
  20. package/codeApp/dist/connectors/azureKeyvault.js +459 -0
  21. package/codeApp/dist/connectors/jira.js +1247 -0
  22. package/codeApp/dist/connectors/office365groups.js +642 -0
  23. package/codeApp/dist/connectors/office365users.js +513 -0
  24. package/codeApp/dist/connectors/outlook.js +1393 -0
  25. package/{examples/kanban/dist → codeApp/dist/connectors}/sharepoint.js +466 -339
  26. package/codeApp/dist/connectors/sql.js +149 -0
  27. package/codeApp/dist/connectors/teams.js +280 -0
  28. package/codeApp/dist/index.js +1 -1
  29. package/codeApp/dist/power-apps-data.js +725 -176
  30. package/codeApp/src/generated/index.ts +12 -0
  31. package/codeApp/src/generated/models/AzureKeyVaultModel.ts +107 -0
  32. package/codeApp/src/generated/models/JiraModel.ts +501 -0
  33. package/codeApp/src/generated/services/AzureKeyVaultService.ts +257 -0
  34. package/codeApp/src/generated/services/JiraService.ts +1124 -0
  35. package/examples/{kanban → apps/kanban}/dist/dataverse.js +94 -94
  36. package/examples/{kanban → apps/kanban}/dist/index.css +605 -605
  37. package/examples/{kanban → apps/kanban}/dist/index.html +21 -21
  38. package/examples/{kanban → apps/kanban}/dist/index.js +860 -860
  39. package/examples/{kanban → apps/kanban}/dist/office365groups.js +97 -97
  40. package/examples/apps/kanban/dist/office365users.js +451 -0
  41. package/examples/{kanban → apps/kanban}/dist/outlook.js +162 -162
  42. package/examples/{planning Poker/dist/power-apps-data.js → apps/kanban/dist/power-apps-data.js} +2953 -2953
  43. package/{dev files/sharepoint.js → examples/apps/kanban/dist/sharepoint.js} +195 -99
  44. package/examples/{kanban → apps/kanban}/power.config.json +35 -35
  45. package/examples/{planning Poker → apps/planning Poker}/additional files/customizations (tables).xml +6428 -6428
  46. package/examples/{planning Poker → apps/planning Poker}/additional files/dataverse-tables.json +165 -165
  47. package/examples/{planning Poker → apps/planning Poker}/additional files/readme.md +122 -122
  48. package/examples/{planning Poker → apps/planning Poker}/dist/dataverse.js +78 -78
  49. package/examples/{planning Poker → apps/planning Poker}/dist/index.html +198 -198
  50. package/examples/{planning Poker → apps/planning Poker}/dist/index.js +954 -954
  51. package/examples/{kanban/dist/power-apps-data.js → apps/planning Poker/dist/power-apps-data.js } +2953 -2953
  52. package/examples/{planning Poker → apps/planning Poker}/dist/styles.css +815 -815
  53. package/examples/{planning Poker → apps/planning Poker}/power.config.json +50 -50
  54. package/examples/{solution explorer → apps/solution explorer}/dist/codeapp.js +1098 -1098
  55. package/examples/{solution explorer → apps/solution explorer}/dist/index.html +80 -80
  56. package/examples/{solution explorer → apps/solution explorer}/dist/index.js +735 -735
  57. package/examples/{solution explorer → apps/solution explorer}/dist/styles.css +571 -571
  58. package/examples/{solution explorer → apps/solution explorer}/power.config.json +150 -150
  59. package/examples/{todo → apps/todo}/dist/dataverse.js +64 -64
  60. package/examples/{todo → apps/todo}/dist/index.html +75 -75
  61. package/examples/{todo → apps/todo}/dist/index.js +8 -8
  62. package/examples/{todo → apps/todo}/dist/power-apps-data.js +2953 -2953
  63. package/examples/{todo → apps/todo}/dist/renderer.js +375 -375
  64. package/examples/{todo → apps/todo}/dist/styles.css +691 -691
  65. package/examples/{todo → apps/todo}/power.config.json +34 -34
  66. package/examples/combined demo/.power/schemas/appschemas/dataSourcesInfo.ts +6275 -7830
  67. package/examples/combined demo/.power/schemas/jira/jira.Schema.json +6903 -0
  68. package/examples/combined demo/.power/schemas/keyvault/keyvault.Schema.json +1600 -0
  69. package/examples/combined demo/.power/schemas/teams/teams.Schema.json +11112 -0
  70. package/examples/combined demo/dist/codeapp.js +394 -1098
  71. package/examples/combined demo/dist/index.html +29 -511
  72. package/examples/combined demo/dist/index.js +490 -470
  73. package/examples/combined demo/dist/office365users.js +513 -0
  74. package/examples/combined demo/dist/outlook.js +1393 -0
  75. package/examples/combined demo/dist/power-apps-data.js +3079 -3006
  76. package/examples/combined demo/dist/styles.css +483 -0
  77. package/examples/combined demo/power.config.json +33 -42
  78. package/examples/combined demo/src/generated/index.ts +12 -14
  79. package/examples/combined demo/src/generated/models/AzureKeyVaultModel.ts +107 -0
  80. package/examples/combined demo/src/generated/models/JiraModel.ts +501 -0
  81. package/examples/combined demo/src/generated/services/AzureKeyVaultService.ts +257 -0
  82. package/examples/combined demo/src/generated/services/JiraService.ts +1124 -0
  83. package/examples/dataverse Demo/dist/codeapp.js +394 -1085
  84. package/examples/{outlook Demo2/OutlookDemo_1_0_0_1.zip → dataverse Demo/dist/icon-512.png} +0 -0
  85. package/examples/dataverse Demo/dist/index.html +146 -54
  86. package/examples/dataverse Demo/dist/index.js +693 -83
  87. package/examples/dataverse Demo/dist/power-apps-data.js +3079 -2911
  88. package/examples/dataverse Demo/dist/styles.css +528 -0
  89. package/examples/dataverse Demo/power.config.json +41 -35
  90. package/examples/dataverse Demo/readme.md +79 -79
  91. package/examples/groups Demo/dist/codeapp.js +394 -1085
  92. package/examples/groups Demo/dist/icon-512.png +0 -0
  93. package/examples/groups Demo/dist/index.html +21 -25
  94. package/examples/groups Demo/dist/index.js +304 -113
  95. package/examples/groups Demo/dist/office365groups.js +642 -0
  96. package/examples/groups Demo/dist/power-apps-data.js +3079 -2911
  97. package/examples/groups Demo/dist/styles.css +509 -0
  98. package/examples/groups Demo/power.config.json +25 -25
  99. package/examples/myProfile/dist/codeapp.js +398 -0
  100. package/examples/myProfile/dist/index.html +21 -184
  101. package/examples/myProfile/dist/index.js +324 -141
  102. package/examples/myProfile/dist/office365users.js +517 -169
  103. package/examples/myProfile/dist/power-apps-data.js +3080 -2953
  104. package/examples/myProfile/dist/styles.css +458 -0
  105. package/examples/myProfile/power.config.json +24 -23
  106. package/examples/outlook Demo/dist/codeapp.js +394 -1085
  107. package/examples/outlook Demo/dist/index.html +150 -35
  108. package/examples/outlook Demo/dist/index.js +516 -170
  109. package/examples/outlook Demo/dist/outlook.js +1393 -121
  110. package/examples/outlook Demo/dist/power-apps-data.js +3079 -2911
  111. package/examples/outlook Demo/dist/styles.css +408 -84
  112. package/examples/outlook Demo/power.config.json +24 -23
  113. package/examples/outlook Demo/readme.md +92 -82
  114. package/examples/sharePoint Demo/dist/codeapp.js +394 -1085
  115. package/examples/sharePoint Demo/dist/icon-512.png +0 -0
  116. package/examples/sharePoint Demo/dist/index.html +22 -255
  117. package/examples/sharePoint Demo/dist/index.js +899 -262
  118. package/examples/sharePoint Demo/dist/power-apps-data.js +3079 -2911
  119. package/examples/sharePoint Demo/dist/sharepoint.js +466 -0
  120. package/examples/sharePoint Demo/dist/styles.css +587 -0
  121. package/examples/sharePoint Demo/power.config.json +23 -22
  122. package/package.json +1 -1
  123. package/readme.md +479 -61
  124. package/.github/instructions/wyattdave.instructions.md +0 -39
  125. package/.vscode/settings.json +0 -6
  126. package/dev files/dataverse.js +0 -105
  127. package/dev files/office365groups.js +0 -65
  128. package/dev files/office365users.js +0 -169
  129. package/dev files/outlook.js +0 -330
  130. package/examples/combined demo/.power/schemas/office365/office365.Schema.json +0 -21098
  131. package/examples/combined demo/.power/schemas/office365users/office365users.Schema.json +0 -2094
  132. package/examples/kanban/agent/decision-log.md +0 -9
  133. package/examples/kanban/agent/mockup-01-editorial-glass.html +0 -159
  134. package/examples/kanban/agent/mockup-02-dark-rail.html +0 -147
  135. package/examples/kanban/agent/mockup-03-paper-grid.html +0 -114
  136. package/examples/kanban/agent/mockup-04-neon-minimal.html +0 -141
  137. package/examples/kanban/agent/mockup-05-mono-architect.html +0 -119
  138. package/examples/kanban/dist/environmentVar.js +0 -55
  139. package/examples/kanban/dist/office365users.js +0 -169
  140. package/examples/kanban/src/generated/index.ts +0 -14
  141. package/examples/outlook Demo2/agent/decision-log.md +0 -7
  142. package/examples/outlook Demo2/dist/codeapp.js +0 -1334
  143. package/examples/outlook Demo2/dist/index.html +0 -98
  144. package/examples/outlook Demo2/dist/index.js +0 -346
  145. package/examples/outlook Demo2/dist/styles.css +0 -639
  146. package/examples/outlook Demo2/power.config.json +0 -23
  147. package/examples/planning Poker/.vscode/settings.json +0 -5
  148. package/examples/sharePoint Demo/agent/decision-log.md +0 -17
  149. package/examples/solution explorer/agent/decision-log.md +0 -27
  150. package/examples/solution explorer/agent/mockup-01-swiss-grid.html +0 -452
  151. package/examples/solution explorer/agent/mockup-02-dark-glass.html +0 -496
  152. package/examples/solution explorer/agent/mockup-03-paper-console.html +0 -510
  153. package/examples/solution explorer/agent/mockup-04-neon-noir.html +0 -546
  154. package/examples/solution explorer/agent/mockup-05-zen-garden.html +0 -534
  155. package/examples/solution explorer/dist/power-apps-data.js +0 -3007
  156. package/scripts/build-power-sdk.mjs +0 -69
  157. /package/{examples/kanban → codeApp}/src/generated/models/Office365GroupsModel.ts +0 -0
  158. /package/{examples/kanban → codeApp}/src/generated/models/Office365OutlookModel.ts +0 -0
  159. /package/{examples/kanban → codeApp}/src/generated/models/Office365UsersModel.ts +0 -0
  160. /package/{examples/kanban → codeApp}/src/generated/services/Office365GroupsService.ts +0 -0
  161. /package/{examples/kanban → codeApp}/src/generated/services/Office365OutlookService.ts +0 -0
  162. /package/{examples/kanban → codeApp}/src/generated/services/Office365UsersService.ts +0 -0
  163. /package/{dev files → examples/apps/kanban/dist}/environmentVar.js +0 -0
  164. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/index.ts +0 -0
  165. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365GroupsModel.ts +0 -0
  166. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365OutlookModel.ts +0 -0
  167. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365UsersModel.ts +0 -0
  168. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365GroupsService.ts +0 -0
  169. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365OutlookService.ts +0 -0
  170. /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365UsersService.ts +0 -0
  171. /package/examples/{planning Poker → apps/planning Poker}/additional files/AgilePoker_1_0_0_1.zip +0 -0
  172. /package/examples/{planning Poker → apps/planning Poker}/additional files/PokerTables_1_0_0_1.zip +0 -0
  173. /package/examples/{outlook Demo2 → apps/solution explorer}/dist/icon-512.png +0 -0
  174. /package/examples/{outlook Demo2 → apps/solution explorer}/dist/power-apps-data.js +0 -0
  175. /package/examples/{todo → apps/todo}/dist/icon192.png +0 -0
  176. /package/examples/{solution explorer → combined demo}/dist/icon-512.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
+ });