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.
- package/AI/codeapp.agent.md +105 -0
- package/AI/skills/connections/SKILL.md +47 -0
- package/AI/skills/dataverse/SKILL.md +99 -0
- package/AI/skills/environment-variables/SKILL.md +89 -0
- package/AI/skills/frontend-design/SKILL.md +34 -0
- package/AI/skills/jira/SKILL.md +81 -0
- package/AI/skills/office365-groups/SKILL.md +61 -0
- package/AI/skills/office365-outlook/SKILL.md +52 -0
- package/AI/skills/office365-users/SKILL.md +78 -0
- package/AI/skills/sharepoint/SKILL.md +77 -0
- package/AI/skills/sql/SKILL.md +85 -0
- package/AI/skills/start/SKILL.md +46 -0
- package/AI/skills/teams/SKILL.md +55 -0
- package/{examples/combined demo/.power/schemas/office365groups/office365groups.Schema.json → codeApp/.power/schemas/office365groups/office365groups.Schema.json} +2203 -2203
- package/codeApp/dist/codeapp.js +95 -1792
- package/codeApp/dist/connectors/azureKeyvault.js +459 -0
- package/codeApp/dist/connectors/jira.js +1247 -0
- package/codeApp/dist/connectors/office365groups.js +642 -0
- package/codeApp/dist/connectors/office365users.js +513 -0
- package/codeApp/dist/connectors/outlook.js +1393 -0
- package/{dev files/sharepoint.js → codeApp/dist/connectors/sharepoint.js} +239 -112
- package/codeApp/dist/connectors/sql.js +149 -0
- package/codeApp/dist/connectors/teams.js +280 -0
- package/codeApp/dist/power-apps-data.js +16 -2
- package/examples/{kanban → apps/kanban}/dist/dataverse.js +94 -94
- package/examples/{kanban → apps/kanban}/dist/environmentVar.js +55 -55
- package/examples/{kanban → apps/kanban}/dist/index.css +605 -605
- package/examples/{kanban → apps/kanban}/dist/index.html +21 -21
- package/examples/{kanban → apps/kanban}/dist/index.js +860 -860
- package/examples/{kanban → apps/kanban}/dist/office365groups.js +97 -97
- package/examples/apps/kanban/dist/office365users.js +451 -0
- package/examples/{kanban → apps/kanban}/dist/outlook.js +162 -162
- package/examples/{planning Poker/dist/power-apps-data.js → apps/kanban/dist/power-apps-data.js} +2953 -2953
- package/examples/{kanban → apps/kanban}/dist/sharepoint.js +435 -339
- package/examples/{kanban → apps/kanban}/power.config.json +35 -35
- package/examples/{planning Poker → apps/planning Poker}/additional files/customizations (tables).xml +6428 -6428
- package/examples/{planning Poker → apps/planning Poker}/additional files/dataverse-tables.json +165 -165
- package/examples/{planning Poker → apps/planning Poker}/additional files/readme.md +122 -122
- package/examples/{planning Poker → apps/planning Poker}/dist/dataverse.js +78 -78
- package/examples/{planning Poker → apps/planning Poker}/dist/index.html +198 -198
- package/examples/{planning Poker → apps/planning Poker}/dist/index.js +954 -954
- package/examples/{todo/dist/power-apps-data.js → apps/planning Poker/dist/power-apps-data.js } +2953 -2953
- package/examples/{planning Poker → apps/planning Poker}/dist/styles.css +815 -815
- package/examples/{planning Poker → apps/planning Poker}/power.config.json +50 -50
- package/examples/{outlook Demo2 → apps/solution explorer}/dist/codeapp.js +9 -245
- package/examples/apps/solution explorer/dist/index.html +80 -0
- package/examples/apps/solution explorer/dist/index.js +735 -0
- package/examples/apps/solution explorer/dist/styles.css +571 -0
- package/examples/apps/solution explorer/power.config.json +151 -0
- package/examples/{todo → apps/todo}/dist/dataverse.js +64 -64
- package/examples/{todo → apps/todo}/dist/index.html +75 -75
- package/examples/{todo → apps/todo}/dist/index.js +8 -8
- package/examples/{kanban → apps/todo}/dist/power-apps-data.js +2953 -2953
- package/examples/{todo → apps/todo}/dist/renderer.js +375 -375
- package/examples/{todo → apps/todo}/dist/styles.css +691 -691
- package/examples/{todo → apps/todo}/power.config.json +34 -34
- package/examples/combined demo/.power/schemas/appschemas/dataSourcesInfo.ts +6275 -7830
- package/examples/combined demo/.power/schemas/jira/jira.Schema.json +6903 -0
- package/examples/combined demo/.power/schemas/keyvault/keyvault.Schema.json +1600 -0
- package/examples/combined demo/.power/schemas/teams/teams.Schema.json +11112 -0
- package/examples/combined demo/dist/codeapp.js +394 -1098
- package/examples/{outlook Demo2/OutlookDemo_1_0_0_1.zip → combined demo/dist/icon-512.png} +0 -0
- package/examples/combined demo/dist/index.html +29 -511
- package/examples/combined demo/dist/index.js +490 -470
- package/examples/combined demo/dist/office365users.js +513 -0
- package/examples/combined demo/dist/outlook.js +1393 -0
- package/examples/combined demo/dist/power-apps-data.js +3079 -3006
- package/examples/combined demo/dist/styles.css +483 -0
- package/examples/combined demo/power.config.json +33 -42
- package/examples/combined demo/src/generated/index.ts +12 -14
- package/examples/combined demo/src/generated/models/AzureKeyVaultModel.ts +107 -0
- package/examples/combined demo/src/generated/models/JiraModel.ts +501 -0
- package/examples/combined demo/src/generated/services/AzureKeyVaultService.ts +257 -0
- package/examples/combined demo/src/generated/services/JiraService.ts +1124 -0
- package/examples/dataverse Demo/dist/codeapp.js +394 -1085
- package/examples/dataverse Demo/dist/icon-512.png +0 -0
- package/examples/dataverse Demo/dist/index.html +146 -54
- package/examples/dataverse Demo/dist/index.js +693 -83
- package/examples/dataverse Demo/dist/power-apps-data.js +3079 -2911
- package/examples/dataverse Demo/dist/styles.css +528 -0
- package/examples/dataverse Demo/power.config.json +41 -35
- package/examples/dataverse Demo/readme.md +79 -79
- package/examples/groups Demo/dist/codeapp.js +394 -1085
- package/examples/groups Demo/dist/icon-512.png +0 -0
- package/examples/groups Demo/dist/index.html +21 -25
- package/examples/groups Demo/dist/index.js +304 -113
- package/examples/groups Demo/dist/office365groups.js +642 -0
- package/examples/groups Demo/dist/power-apps-data.js +3079 -2911
- package/examples/groups Demo/dist/styles.css +509 -0
- package/examples/groups Demo/power.config.json +25 -25
- package/examples/myProfile/dist/codeapp.js +398 -0
- package/examples/myProfile/dist/index.html +21 -184
- package/examples/myProfile/dist/index.js +324 -141
- package/examples/myProfile/dist/office365users.js +517 -169
- package/examples/myProfile/dist/power-apps-data.js +3080 -2953
- package/examples/myProfile/dist/styles.css +458 -0
- package/examples/myProfile/power.config.json +24 -23
- package/examples/outlook Demo/dist/codeapp.js +394 -1085
- package/examples/outlook Demo/dist/index.html +150 -35
- package/examples/outlook Demo/dist/index.js +516 -170
- package/examples/outlook Demo/dist/outlook.js +1393 -121
- package/examples/outlook Demo/dist/power-apps-data.js +3079 -2911
- package/examples/outlook Demo/dist/styles.css +408 -84
- package/examples/outlook Demo/power.config.json +24 -23
- package/examples/outlook Demo/readme.md +92 -82
- package/examples/sharePoint Demo/dist/codeapp.js +394 -1085
- package/examples/sharePoint Demo/dist/icon-512.png +0 -0
- package/examples/sharePoint Demo/dist/index.html +22 -255
- package/examples/sharePoint Demo/dist/index.js +899 -262
- package/examples/sharePoint Demo/dist/power-apps-data.js +3079 -2911
- package/examples/sharePoint Demo/dist/sharepoint.js +466 -0
- package/examples/sharePoint Demo/dist/styles.css +587 -0
- package/examples/sharePoint Demo/power.config.json +23 -22
- package/package.json +1 -1
- package/readme.md +465 -76
- package/.vscode/settings.json +0 -6
- package/dev files/customConnector.js +0 -98
- package/dev files/dataverse.js +0 -120
- package/dev files/environmentVar.js +0 -55
- package/dev files/office365groups.js +0 -65
- package/dev files/office365users.js +0 -169
- package/dev files/outlook.js +0 -330
- package/dev files/power-apps-data.js +0 -2952
- package/examples/combined demo/.power/schemas/office365/office365.Schema.json +0 -21098
- package/examples/combined demo/.power/schemas/office365users/office365users.Schema.json +0 -2094
- package/examples/kanban/agent/decision-log.md +0 -9
- package/examples/kanban/agent/mockup-01-editorial-glass.html +0 -159
- package/examples/kanban/agent/mockup-02-dark-rail.html +0 -147
- package/examples/kanban/agent/mockup-03-paper-grid.html +0 -114
- package/examples/kanban/agent/mockup-04-neon-minimal.html +0 -141
- package/examples/kanban/agent/mockup-05-mono-architect.html +0 -119
- package/examples/kanban/dist/office365users.js +0 -169
- package/examples/kanban/src/generated/index.ts +0 -14
- package/examples/kanban/src/generated/models/Office365GroupsModel.ts +0 -363
- package/examples/kanban/src/generated/models/Office365OutlookModel.ts +0 -2046
- package/examples/kanban/src/generated/models/Office365UsersModel.ts +0 -254
- package/examples/kanban/src/generated/services/Office365GroupsService.ts +0 -326
- package/examples/kanban/src/generated/services/Office365OutlookService.ts +0 -2476
- package/examples/kanban/src/generated/services/Office365UsersService.ts +0 -358
- package/examples/outlook Demo2/agent/decision-log.md +0 -7
- package/examples/outlook Demo2/dist/index.html +0 -98
- package/examples/outlook Demo2/dist/index.js +0 -272
- package/examples/outlook Demo2/dist/styles.css +0 -639
- package/examples/outlook Demo2/power.config.json +0 -23
- package/examples/planning Poker/.vscode/settings.json +0 -5
- package/examples/sharePoint Demo/agent/decision-log.md +0 -17
- /package/examples/{outlook Demo2 → apps/kanban}/src/generated/index.ts +0 -0
- /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365GroupsModel.ts +0 -0
- /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365OutlookModel.ts +0 -0
- /package/examples/{outlook Demo2 → apps/kanban}/src/generated/models/Office365UsersModel.ts +0 -0
- /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365GroupsService.ts +0 -0
- /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365OutlookService.ts +0 -0
- /package/examples/{outlook Demo2 → apps/kanban}/src/generated/services/Office365UsersService.ts +0 -0
- /package/examples/{planning Poker → apps/planning Poker}/additional files/AgilePoker_1_0_0_1.zip +0 -0
- /package/examples/{planning Poker → apps/planning Poker}/additional files/PokerTables_1_0_0_1.zip +0 -0
- /package/examples/{outlook Demo2 → apps/solution explorer}/dist/icon-512.png +0 -0
- /package/examples/{outlook Demo2 → apps/solution explorer}/dist/power-apps-data.js +0 -0
- /package/examples/{todo → apps/todo}/dist/icon192.png +0 -0
|
@@ -1,262 +1,899 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
'
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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'), '&')
|
|
881
|
+
.replace(new RegExp('<', 'g'), '<')
|
|
882
|
+
.replace(new RegExp('>', 'g'), '>')
|
|
883
|
+
.replace(new RegExp('"', 'g'), '"');
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function escapeAttribute(sValue) {
|
|
887
|
+
return escapeHtml(sValue).replace(new RegExp("'", 'g'), ''');
|
|
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
|
+
});
|