Pnpjs: Set list default values

Created on 15 Jan 2019  路  4Comments  路  Source: pnp/pnpjs

Category

  • [X] Enhancement
  • [ ] Bug
  • [ ] Question
  • [ ] Documentation gap/issue

Version

Please specify what version of the library you are using: [1.2.8]

Please specify what version(s) of SharePoint you are targeting: [SharePoint Online]

Expected / Desired Behavior / Question

I have read some threads about that set list default values is not possible using REST api, but I have got it to work using the code below using only REST ap. However it requires a number of requests and I dont know if its an requirement for this library to be one request.
I share the code that i'm using, it needs some modifications in the file edit to support default values on folders, but it should be rather easy to fix.

Hope it could be useful for anyone implementing this feature in pnp-js.

Kristian

import { ApplicationCustomizerContext } from '@microsoft/sp-application-base';
import { SPWeb } from '@microsoft/sp-page-context';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';

export interface IListDefaultValue {
  fieldName: string;
  fieldValue: string;
}

export class ListDefaultValues {
  public static set(context: ApplicationCustomizerContext, internalListName: string, values: IListDefaultValue[]): Promise<void> {
    return new Promise((resolve, reject) => {
      const currentWeb: SPWeb = context.pageContext.web;
      context.spHttpClient
        .get(
          `${currentWeb.absoluteUrl}/_api/Web/GetFileByServerRelativePath` +
          `(decodedurl='${currentWeb.serverRelativeUrl}/Shared%20Documents/Forms/client_LocationBasedDefaults.html')/$value`,
          SPHttpClient.configurations.v1
        )
        .then((htmlContent: SPHttpClientResponse) => {
          htmlContent.text()
            .then(body => {
              const parser: DOMParser = new DOMParser();
              const newMetadata: boolean = body.indexOf('<MetadataDefaults>') === -1;
              const xmlDoc: Document = parser.parseFromString(
                newMetadata
                  ? `<MetadataDefaults><a href='${currentWeb.serverRelativeUrl}/${internalListName}'></a></MetadataDefaults>`
                  : body,
                'text/xml'
              );
              let aValue: Node = xmlDoc.firstChild.firstChild;
              for (let value of values) {
                const newElement: string = `<DefaultValue FieldName='${value.fieldName}'>${value.fieldValue}</DefaultValue>`;
                let found: boolean = false;
                for (let i: number = 0; i < aValue.childNodes.length; i++) {
                  if ((aValue.childNodes[i] as Element).getAttribute('FieldName') === value.fieldName) {
                    found = true;
                    if ((aValue.childNodes[i] as Element).innerHTML !== value.fieldValue) {
                      (aValue.childNodes[i] as Element).outerHTML = newElement;
                    }
                    break;
                  }
                }
                if (!found) {
                  const dummyElem: Element = xmlDoc.createElement(`Dummy`);
                  dummyElem.innerHTML = newElement;
                  aValue.insertBefore(dummyElem.firstChild, aValue.firstChild);
                }
              }
              if ((xmlDoc.firstChild as Element).outerHTML === body) {
                resolve();
                return;
              }
              const allProms: Promise<void>[] = [];
              allProms.push(
                context.spHttpClient.post(
                  `${currentWeb.absoluteUrl}/_api/Web/` +
                  `GetFolderByServerRelativePath(decodedurl='${currentWeb.serverRelativeUrl}/${internalListName}/Forms')` +
                  `/Files/add(url='client_LocationBasedDefaults.html',overwrite=true)`,
                  SPHttpClient.configurations.v1,
                  { body: (xmlDoc.firstChild as Element).outerHTML }
                ).then((response: SPHttpClientResponse) => {
                  if (!response.ok) {
                    reject(response.statusText);
                  }
                })
              );
              if (newMetadata) {
                let eventReceivePayload: any = {
                  eventReceiverCreationInformation: {
                    EventType: 10001,
                    Synchronization: 1,
                    ReceiverAssembly: 'Microsoft.Office.DocumentManagement, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c',
                    ReceiverClass: 'Microsoft.Office.DocumentManagement.LocationBasedMetadataDefaultsReceiver',
                    ReceiverName: 'LocationBasedMetadataDefaultsReceiver ItemAdded',
                    SequenceNumber: 1000
                  }
                };
                allProms.push(
                  context.spHttpClient.post(
                    `${currentWeb.absoluteUrl}/_api/web/GetList('${currentWeb.serverRelativeUrl}/${internalListName}')/eventReceivers/add`,
                    SPHttpClient.configurations.v1, { body: JSON.stringify(eventReceivePayload) }
                  ).then((response: SPHttpClientResponse) => {
                    if (!response.ok) {
                      reject(response.statusText);
                    }
                  })
                );
              }
              Promise.all(allProms).then(_ => resolve()).catch(reject);
            })
            .catch(reject)
        })
        .catch(reject);
    });
  }
}
code investigate enhancement

Most helpful comment

Don't mind at all, liked your implementation.
Adjusted the length check below.

import { Web, SPHttpClient } from '@pnp/sp';

export interface IListDefaultValue {
    fieldName: string;
    fieldValue: string;
}

export class ListDefaultValues {
    constructor(private web: Web, private serverRelativeUrl: string) { }

    public async set(listUri: string, values: IListDefaultValue[]): Promise<void> {
        const client = new SPHttpClient();
        const body = await this.web.getFileByServerRelativePath(
            `${this.serverRelativeUrl}/${listUri}/Forms/client_LocationBasedDefaults.html`
        ).getText();
        const parser: DOMParser = new DOMParser();
        const newMetadata: boolean = ''.indexOf('<MetadataDefaults>') === -1;
        const xmlDoc: Document = parser.parseFromString(
            newMetadata
                ? `<MetadataDefaults><a href='${this.serverRelativeUrl}/${listUri}'></a></MetadataDefaults>`
                : body,
            'text/xml'
        );
        const aValue: Node = xmlDoc.firstChild.firstChild;
        for (const { fieldName, fieldValue } of values) {
            const newElement: string = `<DefaultValue FieldName='${fieldName}'>${fieldValue}</DefaultValue>`;
            if ([].filter.call(aValue.childNodes, (el: Element) => {
                if (el.getAttribute('FieldName') === fieldName) {
                    if (el.innerHTML !== fieldValue) {
                        el.outerHTML = newElement;
                    }
                    return true;
                }
                return false;
            }).length === 0) {
                const dummyElem: Element = xmlDoc.createElement('Dummy');
                dummyElem.innerHTML = newElement;
                aValue.insertBefore(dummyElem.firstChild, aValue.firstChild);
            }
        }
        const firstChild = xmlDoc.firstChild as Element;
        if (firstChild.outerHTML === body) {
            return;
        }

        await this.web.getFolderByServerRelativePath(`${this.serverRelativeUrl}/${listUri}/Forms`)
            .files.add('client_LocationBasedDefaults.html', firstChild.outerHTML);
        if (newMetadata) {
            const eventReceivePayload: any = {
                eventReceiverCreationInformation: {
                    EventType: 10001,
                    Synchronization: 1,
                    ReceiverAssembly: 'Microsoft.Office.DocumentManagement, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c',
                    ReceiverClass: 'Microsoft.Office.DocumentManagement.LocationBasedMetadataDefaultsReceiver',
                    ReceiverName: 'LocationBasedMetadataDefaultsReceiver ItemAdded',
                    SequenceNumber: 1000
                }
            };
            await client.post(this.web.getList(`${this.serverRelativeUrl}/${listUri}`).eventReceivers.toUrl(), {
                body: JSON.stringify(eventReceivePayload)
            });
        }
    }
}

All 4 comments

Hi @thenail,

Regards your code. it can be rewritten with PnPjs in the following manner:

import { Web, SPHttpClient } from '@pnp/sp';

export interface IListDefaultValue {
  fieldName: string;
  fieldValue: string;
}

export class ListDefaultValues {
  constructor(private web: Web, private serverRelativeUrl: string) {}

  public async set(listUri: string, values: IListDefaultValue[]): Promise<void> {
    const client = new SPHttpClient();
    const body = await this.web.getFileByServerRelativePath(
      `${this.serverRelativeUrl}/Shared%20Documents/Forms/client_LocationBasedDefaults.html`
    ).getText();
    const parser: DOMParser = new DOMParser();
    const newMetadata: boolean = body.indexOf('<MetadataDefaults>') === -1;
    const xmlDoc: Document = parser.parseFromString(
      newMetadata
        ? `<MetadataDefaults><a href='${this.serverRelativeUrl}/${listUri}'></a></MetadataDefaults>`
        : body,
      'text/xml'
    );
    const aValue: Node = xmlDoc.firstChild.firstChild;
    for (const { fieldName, fieldValue } of values) {
      const newElement: string = `<DefaultValue FieldName='${fieldName}'>${fieldValue}</DefaultValue>`;
      if ([].filter.call(aValue.childNodes, (el: Element) => {
        if (el.getAttribute('FieldName') === fieldName) {
          if (el.innerHTML !== fieldValue) {
            el.outerHTML = newElement;
          }
          return true;
        }
        return false;
      }).length === 0) {
        const dummyElem: Element = xmlDoc.createElement('Dummy');
        dummyElem.innerHTML = newElement;
        aValue.insertBefore(dummyElem.firstChild, aValue.firstChild);
      };
    }
    const firstChild = xmlDoc.firstChild as Element;
    if (firstChild.outerHTML === body) {
      return;
    }
    await this.web.getFolderByServerRelativePath(`${this.serverRelativeUrl}/${listUri}/Forms`)
      .files.add('client_LocationBasedDefaults.html', firstChild.outerHTML);
    if (newMetadata) {
      const eventReceivePayload: any = {
        eventReceiverCreationInformation: {
          EventType: 10001,
          Synchronization: 1,
          ReceiverAssembly: 'Microsoft.Office.DocumentManagement, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c',
          ReceiverClass: 'Microsoft.Office.DocumentManagement.LocationBasedMetadataDefaultsReceiver',
          ReceiverName: 'LocationBasedMetadataDefaultsReceiver ItemAdded',
          SequenceNumber: 1000
        }
      };
      await client.post(this.web.getList(`${this.serverRelativeUrl}/${listUri}`).eventReceivers.toUrl(), {
        body: JSON.stringify(eventReceivePayload)
      });
    }
  }
}

And thank you for the idea of a feature to implement.

UPD: Fixed the markdown formatting of your sample, hope you don't mind.

Don't mind at all, liked your implementation.
Adjusted the length check below.

import { Web, SPHttpClient } from '@pnp/sp';

export interface IListDefaultValue {
    fieldName: string;
    fieldValue: string;
}

export class ListDefaultValues {
    constructor(private web: Web, private serverRelativeUrl: string) { }

    public async set(listUri: string, values: IListDefaultValue[]): Promise<void> {
        const client = new SPHttpClient();
        const body = await this.web.getFileByServerRelativePath(
            `${this.serverRelativeUrl}/${listUri}/Forms/client_LocationBasedDefaults.html`
        ).getText();
        const parser: DOMParser = new DOMParser();
        const newMetadata: boolean = ''.indexOf('<MetadataDefaults>') === -1;
        const xmlDoc: Document = parser.parseFromString(
            newMetadata
                ? `<MetadataDefaults><a href='${this.serverRelativeUrl}/${listUri}'></a></MetadataDefaults>`
                : body,
            'text/xml'
        );
        const aValue: Node = xmlDoc.firstChild.firstChild;
        for (const { fieldName, fieldValue } of values) {
            const newElement: string = `<DefaultValue FieldName='${fieldName}'>${fieldValue}</DefaultValue>`;
            if ([].filter.call(aValue.childNodes, (el: Element) => {
                if (el.getAttribute('FieldName') === fieldName) {
                    if (el.innerHTML !== fieldValue) {
                        el.outerHTML = newElement;
                    }
                    return true;
                }
                return false;
            }).length === 0) {
                const dummyElem: Element = xmlDoc.createElement('Dummy');
                dummyElem.innerHTML = newElement;
                aValue.insertBefore(dummyElem.firstChild, aValue.firstChild);
            }
        }
        const firstChild = xmlDoc.firstChild as Element;
        if (firstChild.outerHTML === body) {
            return;
        }

        await this.web.getFolderByServerRelativePath(`${this.serverRelativeUrl}/${listUri}/Forms`)
            .files.add('client_LocationBasedDefaults.html', firstChild.outerHTML);
        if (newMetadata) {
            const eventReceivePayload: any = {
                eventReceiverCreationInformation: {
                    EventType: 10001,
                    Synchronization: 1,
                    ReceiverAssembly: 'Microsoft.Office.DocumentManagement, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c',
                    ReceiverClass: 'Microsoft.Office.DocumentManagement.LocationBasedMetadataDefaultsReceiver',
                    ReceiverName: 'LocationBasedMetadataDefaultsReceiver ItemAdded',
                    SequenceNumber: 1000
                }
            };
            await client.post(this.web.getList(`${this.serverRelativeUrl}/${listUri}`).eventReceivers.toUrl(), {
                body: JSON.stringify(eventReceivePayload)
            });
        }
    }
}

The only issue with this implementation is the use of DOMParser which only works in the browser and not from nodejs.

I believe this is a great example of how this can be done - but I don't feel it is something we need to add to the library at this time. Appreciate you sharing it and we will definitely refer folks to this example. Thanks!

Was this page helpful?
0 / 5 - 0 ratings