evont-software.com

Email: info@evont-software.com

Javascript - Publish classic list form webparts

Category:SPFX
Date:
Author: Mathias Osterkamp

Problem

For classic SharePoint 2019 libraries you still need your EditForm.aspx and DispForm.aspx to edit your properties. If you like to make any changes, create another form page or repair it by code, it is a little bit more complicated. Most time you try to edit the "DefaultEditFormUrl" property of your list. If you try to change the "DefaultEditFormUrl" property it leads some time to the following error:

Unable to find an SPForm matching URL...

The reason is, sharepoint evaluates your target url and try to find the form on the page. So you have also to upload the list form webpart.

Solution

Here i show some helpers to realise this with javascript code. Most samples on internet are powershell, but doing this in a SPFX solution it is also very smart. We use here some help from pnp js (https://pnp.github.io/pnpjs/) framework. You can download the content of the aspx file from a existing library by open your folder in internet explorer and copy the file.

Upload your editform.aspx

1const folderUrl = '/sites/sitecollection/library/';
2const dispFormUrl = `${folderUrl}/Forms/EditForm.aspx`;
3const content = `..........`; //replace your content
4await this.uploadFile(folderUrl, dispFormUrl, content);
5
6/**
7 * Upload a file from a library
8 *
9 * @param folderUrl relative path of a library or the folder in a library
10 * @param fileUrl new relative url of the file
11 * @param content content of the file
12 */
13public async uploadFile(folderUrl: string, fileUrl: string, content: Blob | ArrayBuffer | string): Promise<void> {
14 const props: IAddUsingPathProps = { Overwrite: true };
15 await sp.web.getFolderByServerRelativeUrl(folderUrl).files.addUsingPath(fileUrl, content, props);
16}


Import Webparts

You need to import the list webpart, don't forget to set the listid. Your class should also have a property for spHttpClient and absoluteWebUrl from pnp js.


1const listInfoId:string = 'xxxxx-xx-xxxx-..';
2
3const editwebpartxml = `<?xml version="1.0" encoding="utf-8"?>
4 <WebPart xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/WebPart/v2">
5 <Title />
6 <FrameType>Default</FrameType>
7 <Description />
8 <IsIncluded>true</IsIncluded>
9 <ZoneID>Main</ZoneID>
10 <PartOrder>1</PartOrder>
11 <FrameState>Normal</FrameState>
12 <Height />
13 <Width />
14 <AllowRemove>true</AllowRemove>
15 <AllowZoneChange>true</AllowZoneChange>
16 <AllowMinimize>true</AllowMinimize>
17 <AllowConnect>true</AllowConnect>
18 <AllowEdit>true</AllowEdit>
19 <AllowHide>true</AllowHide>
20 <IsVisible>true</IsVisible>
21 <DetailLink />
22 <HelpLink />
23 <HelpMode>Modeless</HelpMode>
24 <Dir>Default</Dir>
25 <PartImageSmall />
26 <MissingAssembly>Dieses Webpart kann nicht importiert werden.</MissingAssembly>
27 <PartImageLarge />
28 <IsIncludedFilter />
29 <Assembly>Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
30 <TypeName>Microsoft.SharePoint.WebPartPages.ListFormWebPart</TypeName>
31 <ListName xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">{${listInfoId}}</ListName>
32 <ListId xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">${listInfoId}</ListId>
33 <PageType xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">PAGE_EDITFORM</PageType>
34 <FormType xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">6</FormType>
35 <ControlMode xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">Edit</ControlMode>
36 <ViewFlag xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">1048576</ViewFlag>
37 <ViewFlags xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">Default</ViewFlags>
38 <ListItemId xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">0</ListItemId>
39 </WebPart>`;
40
41const displaywebpartxml = `<?xml version="1.0" encoding="utf-8"?>
42 <WebPart xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/WebPart/v2">
43 <Title />
44 <FrameType>Default</FrameType>
45 <Description />
46 <IsIncluded>true</IsIncluded>
47 <ZoneID>Main</ZoneID>
48 <PartOrder>1</PartOrder>
49 <FrameState>Normal</FrameState>
50 <Height />
51 <Width />
52 <AllowRemove>true</AllowRemove>
53 <AllowZoneChange>true</AllowZoneChange>
54 <AllowMinimize>true</AllowMinimize>
55 <AllowConnect>true</AllowConnect>
56 <AllowEdit>true</AllowEdit>
57 <AllowHide>true</AllowHide>
58 <IsVisible>true</IsVisible>
59 <DetailLink />
60 <HelpLink />
61 <HelpMode>Modeless</HelpMode>
62 <Dir>Default</Dir>
63 <PartImageSmall />
64 <MissingAssembly>Dieses Webpart kann nicht importiert werden.</MissingAssembly>
65 <PartImageLarge />
66 <IsIncludedFilter />
67 <Assembly>Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
68 <TypeName>Microsoft.SharePoint.WebPartPages.ListFormWebPart</TypeName>
69 <ListName xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">{${listInfoId}}</ListName>
70 <ListId xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">${listInfoId}</ListId>
71 <PageType xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">PAGE_DISPLAYFORM</PageType>
72 <FormType xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">4</FormType>
73 <ControlMode xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">Display</ControlMode>
74 <ViewFlag xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">1048576</ViewFlag>
75 <ViewFlags xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">Default</ViewFlags>
76 <ListItemId xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">0</ListItemId>
77 </WebPart>`;
78
79
80await this.importClassicPageWebpart(dispFormUrl, 'Main', 1, displaywebpartxml);
81await this.importClassicPageWebpart(editFormUrl, 'Main', 1, editwebpartxml);
82
83
84/**
85 * Imports webpart on classic page
86 * @param pageUrl
87 * @param zone Main
88 * @param order 1
89 * @param webpartxml
90 * @returns
91 */
92public async importClassicPageWebpart(
93 pageUrl: string,
94 zone: string,
95 order: number,
96 webpartxml: string
97): Promise<void> {
98 const webid = (await sp.web.get()).Id;
99 const siteid = (await sp.site.get()).Id;
100 webpartxml = this.encodeXml(webpartxml);
101 const requestBody = `<?xml version="1.0" encoding="UTF-8"?>
102 <Request
103 xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="Javascript Library">
104 <Actions>
105 <ObjectPath Id="41" ObjectPathId="40" />
106 <ObjectPath Id="43" ObjectPathId="42" />
107 <ObjectIdentityQuery Id="44" ObjectPathId="42" />
108 <ObjectPath Id="46" ObjectPathId="45" />
109 <ObjectPath Id="48" ObjectPathId="47" />
110 <ObjectIdentityQuery Id="49" ObjectPathId="47" />
111 <Query Id="50" ObjectPathId="45">
112 <Query SelectAllProperties="true">
113 <Properties />
114 </Query>
115 </Query>
116 </Actions>
117 <ObjectPaths>
118 <Method Id="40" ParentId="28" Name="GetLimitedWebPartManager">
119 <Parameters>
120 <Parameter Type="Number">1</Parameter>
121 </Parameters>
122 </Method>
123 <Method Id="42" ParentId="40" Name="ImportWebPart">
124 <Parameters>
125 <Parameter Type="String">${webpartxml}</Parameter>
126 </Parameters>
127 </Method>
128 <Property Id="45" ParentId="42" Name="WebPart" />
129 <Method Id="47" ParentId="40" Name="AddWebPart">
130 <Parameters>
131 <Parameter ObjectPathId="45" />
132 <Parameter Type="String">${zone}</Parameter>
133 <Parameter Type="Number">${order}</Parameter>
134 </Parameters>
135 </Method>
136 <Identity Id="28" Name="82d7c99e-f015-0000-6402-fdbb9f2aa54d|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:${siteid}:web:${webid}:file:${pageUrl}" />
137 </ObjectPaths>
138</Request>`;
139 return await this.postcsom(requestBody).then((result) => {
140 if (result[0].ErrorInfo !== null) {
141 throw new Error(JSON.stringify(result[0].ErrorInfo));
142 }
143 return;
144 });
145}
146
147
148/**
149 * xml encodes a string
150 * @param str
151 * @returns
152 */
153private encodeXml(str: string): string {
154 const xml_special_to_escaped_one_map = {
155 '&': '&amp;',
156 '"': '&quot;',
157 '<': '&lt;',
158 '>': '&gt;'
159 };
160
161 return str.replace(/([\&"<>])/g, function (str, item) {
162 return xml_special_to_escaped_one_map[item];
163 });
164}
165
166
167
168
169/**
170 * sends a request to client svc
171 * @param data request data
172 */
173private async postcsom(data: string): Promise<any> {
174 const clientServiceUrl = this.absoluteWebUrl + '/_vti_bin/client.svc/ProcessQuery';
175
176 const formDigest = await this.getFormDigest();
177
178 const requestHeaders: Headers = new Headers();
179 requestHeaders.append('Accept', 'application/json');
180 requestHeaders.append('Content-Type', 'text/xml');
181 requestHeaders.append('X-RequestDigest', formDigest);
182
183 const httpPostOptions: IHttpClientOptions = {
184 headers: requestHeaders,
185 body: data
186 };
187
188 const serviceResponse: IResponse = await this.spHttpClient.post(clientServiceUrl,SPHttpClient.configurations.v1, httpPostOptions);
189 const serviceJSONResponse = await serviceResponse.json();
190 if (serviceResponse.ok) {
191 return serviceJSONResponse;
192 }
193 return null;
194}
195
196/**
197 * digest is needed for post requests
198 */
199private async getFormDigest(): Promise<string> {
200 const contextInfoUrl = this.absoluteWebUrl + '/_api/contextinfo';
201
202 const requestHeaders: Headers = new Headers();
203 requestHeaders.append('Accept', 'application/json');
204 requestHeaders.append('Content-Type', 'text/xml');
205
206 const httpPostOptions: IHttpClientOptions = {
207 headers: requestHeaders
208 };
209 const contextInfoResponse: IResponse = await this.spHttpClient.post(contextInfoUrl, httpPostOptions);
210 const contextInfoJsonResponse = await contextInfoResponse.json();
211 const formDigest: string = contextInfoJsonResponse.FormDigestValue;
212
213 return formDigest;
214}


Update DefaultDisplayFormUrl

Finally you can update your list form without errors.

1await this.updateList(listTitle, {
2 DefaultDisplayFormUrl: dispFormUrl,
3 DefaultEditFormUrl: editFormUrl,
4 ContentTypesEnabled: false
5 });
6
7/**
8 * Updates List Properties
9 * @param listTitle
10 * @param properties of type IListInfo possible
11 */
12public async updateList(listTitle: string, properties: Record<string, unknown>): Promise<void> {
13 await sp.web.lists.getByTitle(listTitle).update(properties);
14}


By the way, you see also a easy method how to interact with jsom api, without having the complete api included.