Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Talvez você queira usar a biblioteca @pnp/sp, quando criar as web parts da SPFx (Estrutura do SharePoint). Essa biblioteca fornece uma API fluente para tornar intuitiva a construção das suas consultas REST, bem como aceita o envio em lotes e o armazenamento em cache. Para obter mais informações, confira a página inicial do projeto, na qual há links para a documentação, amostras e outros recursos para ajudá-lo a começar.
Observação
O PnPJS é uma solução de software livre com uma comunidade ativa de suporte. Não há nenhuma SLA para o suporte da ferramenta de software livre por parte da Microsoft.
Você pode baixar a fonte completa deste artigo no site de amostras.
Observação
Antes de seguir as etapas deste artigo, configure seu ambiente de desenvolvimento de Web Parts do lado do cliente do SharePoint.
Criar um novo projeto
Crie uma nova pasta para o projeto usando seu console preferido:
md spfx-pnp-js-example
Entre na pasta:
cd spfx-pnp-js-example
Execute o gerador Yeoman do SPFx:
yo @microsoft/sharepoint
Digite os seguintes valores quando solicitado durante a configuração do novo projeto:
- spfx-pnp-js-example como o nome da solução (manter padrão)
- Descrição spfx-pnp-js-example como a descrição da solução (mantenha o padrão)
- Somente SharePoint Online (último) como a versão de pacotes de linha de base
- N para permitir o acesso a APIs Web exclusivas
- Web Part como o componente a ser criado
- SPPnPJSExample como o nome da web part
- Descrição PnPJSExample como a descrição
- React como a estrutura
Abra o projeto no editor de código de sua escolha. As capturas de tela mostradas aqui demonstram o Visual Studio Code. Para abrir a pasta dentro do Visual Studio Code, insira o seguinte no console:
code .
Configure o local do seu workbench hospedado do SharePoint modificando o valor
initialPage
no config/serve.json para apontar para seu locatário/site.
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://{your tenant name}/sites/dev/_layouts/15/workbench.aspx"
}
Instalar e configurar @pnp/sp
Depois que seu projeto for criado, você deve instalar e configurar o @pnp/sp pacote. Além disso, usaremos a @pnp/logging extensão, mas isso é opcional. Essas etapas são comuns para qualquer tipo de projeto (React etc.).
npm install @pnp/logging @pnp/sp --save
Para SPFx versão 1.14.x ou aquelas que não dão suporte a typescript v4.x
Observação: PnPjs versão 3.x só tem suporte no SPFx versão 1.14 e superior e NodeJs versão 12.x e superior.
Atualize o compilador do rush stack para 4.2. Isso é abordado neste excelente artigo do Elio, mas as etapas estão listadas abaixo.
- Desinstale o compilador existente do rush stack (substitua o x pela versão instalada no arquivo package.json):
npm uninstall @microsoft/rush-stack-compiler-3.x
- Instale a versão 4.2:
npm i @microsoft/rush-stack-compiler-4.2
- Atualize tsconfig.json para estender a configuração 4.2:
"extends": "./node_modules/@microsoft/rush-stack-compiler-4.2/includes/tsconfig-web.json"
- Desinstale o compilador existente do rush stack (substitua o x pela versão instalada no arquivo package.json):
Substitua o conteúdo do gulpfile.js por:
'use strict'; const build = require('@microsoft/sp-build-web'); build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); var getTasks = build.rig.getTasks; build.rig.getTasks = function () { var result = getTasks.call(build.rig); result.set('serve', result.get('serve-deprecated')); return result; }; // ********* ADDED ******* // disable tslint build.tslintCmd.enabled = false; // ********* ADDED ******* build.initialize(require('gulp'));
Atualizar onInit no PnPjsExampleWebPart.ts
Como a @pnp/sp biblioteca constrói solicitações REST, ela precisa saber a URL para enviar essas solicitações. Ao operar no SPFx, precisamos confiar no objeto de contexto fornecido pela estrutura.
Existem duas maneiras de garantir que você configurou corretamente suas solicitações; usaremos o método onInit
neste exemplo.
Abra o arquivo src\webparts\spPnPjsExample\SpPnPjsExampleWebPart.ts e adicione uma instrução de importação para o arquivo de configuração do projeto pnp (mais neste arquivo abaixo):
import { getSP } from './pnpjsConfig';
onInit()
No método, atualize o código a ser exibido da seguinte maneira. Adicione a chamada para inicializar nossa configuração de projeto após asuper.onInit()
chamada. Fazemos isso depois dosuper.onInit()
para garantir que a estrutura tenha a chance de inicializar tudo o que for necessário e que estamos configurando a biblioteca depois que essas etapas forem concluídas./** * Initialize the web part. */ public async onInit(): Promise<void> { this._environmentMessage = this._getEnvironmentMessage(); await super.onInit(); //Initialize our _sp object that we can then use in other packages without having to pass around the context. // Check out pnpjsConfig.ts for an example of a project setup file. getSP(this.context); }
Adicione um arquivo de configuração de projeto
Em seguida, vamos criar um arquivo de configuração de projeto para PnPjs. Esse arquivo nos permite configurar as importações necessárias para o projeto, bem como inicializar uma instância do objeto sp para uso em qualquer um de nossos outros componentes.
Observe todas as importações para webs, listas, itens e envio em lote. Em nosso componente, faremos chamadas para obter itens de uma biblioteca, portanto, precisamos incluir essas importações para referência futura. Além disso, criamos uma variável que conterá nossa instância configurada do SharePoint Querable
que será criada com a instância de fábrica. Se você se lembrar de nossa função onInit
acima, chamaremos o getSP exportado com o contexto SPFx passado como uma propriedade. Ao fazer isso, podemos estabelecer o contexto com a biblioteca PnPjs para que possamos fazer chamadas posteriormente para a API do SharePoint. Chamadas subsequentes para getSP
sem o contexto retornarão o objeto que já foi configurado.
Este exemplo também mostra como podemos adicionar um behavior
à instância que habilita o registro em log para todas as chamadas. Isso usará o log padrão, mas podemos expandir para incluir nossas próprias funções de registro em log.
import { WebPartContext } from "@microsoft/sp-webpart-base";
// import pnp and pnp logging system
import { spfi, SPFI, SPFx } from "@pnp/sp";
import { LogLevel, PnPLogging } from "@pnp/logging";
import "@pnp/sp/webs";
import "@pnp/sp/lists";
import "@pnp/sp/items";
import "@pnp/sp/batching";
var _sp: SPFI = null;
export const getSP = (context?: WebPartContext): SPFI => {
if (_sp === null && context != null) {
//You must add the @pnp/logging package to include the PnPLogging behavior it is no longer a peer dependency
// The LogLevel set's at what level a message will be written to the console
_sp = spfi().using(SPFx(context)).using(PnPLogging(LogLevel.Warning));
}
return _sp;
};
Adicione um arquivo de interface para o modelo de dados
Adicione um novo arquivo na raiz da pasta de componentes chamada interfaces.ts
. Substitua o conteúdo pelas definições a seguir, que serão referenciadas por nosso componente.
// create File item to work with it internally
export interface IFile {
Id: number;
Title: string;
Name: string;
Size: number;
}
// create PnP JS response interface for File
export interface IResponseFile {
Length: number;
}
// create PnP JS response interface for Item
export interface IResponseItem {
Id: number;
File: IResponseFile;
FileLeafRef: string;
Title: string;
}
Atualize o componente padrão
Por fim, precisamos fazer uma limpeza para criar nosso componente baseado. Primeiro, substitua todo o conteúdo do arquivo PnPjsExample.tsx pelo código a seguir.
import * as React from 'react';
import styles from './PnPjsExample.module.scss';
import { IPnPjsExampleProps } from './IPnPjsExampleProps';
// import interfaces
import { IFile, IResponseItem } from "./interfaces";
import { Caching } from "@pnp/queryable";
import { getSP } from "../pnpjsConfig";
import { SPFI, spfi } from "@pnp/sp";
import { Logger, LogLevel } from "@pnp/logging";
import { IItemUpdateResult } from "@pnp/sp/items";
import { Label, PrimaryButton } from '@microsoft/office-ui-fabric-react-bundle';
export interface IAsyncAwaitPnPJsProps {
description: string;
}
export interface IIPnPjsExampleState {
items: IFile[];
errors: string[];
}
export default class PnPjsExample extends React.Component<IPnPjsExampleProps, IIPnPjsExampleState> {
private LOG_SOURCE = "🅿PnPjsExample";
private LIBRARY_NAME = "Documents";
private _sp: SPFI;
constructor(props: IPnPjsExampleProps) {
super(props);
// set initial state
this.state = {
items: [],
errors: []
};
this._sp = getSP();
}
public componentDidMount(): void {
// read all file sizes from Documents library
this._readAllFilesSize();
}
public render(): React.ReactElement<IAsyncAwaitPnPJsProps> {
// calculate total of file sizes
const totalDocs: number = this.state.items.length > 0
? this.state.items.reduce<number>((acc: number, item: IFile) => {
return (acc + Number(item.Size));
}, 0)
: 0;
return (
<div className={styles.pnPjsExample}>
<Label>Welcome to PnP JS Version 3 Demo!</Label>
<PrimaryButton onClick={this._updateTitles}>Update Item Titles</PrimaryButton>
<Label>List of documents:</Label>
<table width="100%">
<tr>
<td><strong>Title</strong></td>
<td><strong>Name</strong></td>
<td><strong>Size (KB)</strong></td>
</tr>
{this.state.items.map((item, idx) => {
return (
<tr key={idx}>
<td>{item.Title}</td>
<td>{item.Name}</td>
<td>{(item.Size / 1024).toFixed(2)}</td>
</tr>
);
})}
<tr>
<td></td>
<td><strong>Total:</strong></td>
<td><strong>{(totalDocs / 1024).toFixed(2)}</strong></td>
</tr>
</table>
</div >
);
}
private _readAllFilesSize = async (): Promise<void> => {
try {
// do PnP JS query, some notes:
// - .expand() method will retrive Item.File item but only Length property
// - .get() always returns a promise
// - await resolves proimises making your code act syncronous, ergo Promise<IResponseItem[]> becomes IResponse[]
//Extending our sp object to include caching behavior, this modification will add caching to the sp object itself
//this._sp.using(Caching({store:"session"}));
//Creating a new sp object to include caching behavior. This way our original object is unchanged.
const spCache = spfi(this._sp).using(Caching({store:"session"}));
const response: IResponseItem[] = await spCache.web.lists
.getByTitle(this.LIBRARY_NAME)
.items
.select("Id", "Title", "FileLeafRef", "File/Length")
.expand("File/Length")();
// use map to convert IResponseItem[] into our internal object IFile[]
const items: IFile[] = response.map((item: IResponseItem) => {
return {
Id: item.Id,
Title: item.Title || "Unknown",
Size: item.File?.Length || 0,
Name: item.FileLeafRef
};
});
// Add the items to the state
this.setState({ items });
} catch (err) {
Logger.write(`${this.LOG_SOURCE} (_readAllFilesSize) - ${JSON.stringify(err)} - `, LogLevel.Error);
}
}
private _updateTitles = async (): Promise<void> => {
try {
//Will create a batch call that will update the title of each item
// in the library by adding `-Updated` to the end.
const [batchedSP, execute] = this._sp.batched();
//Clone items from the state
const items = JSON.parse(JSON.stringify(this.state.items));
let res: IItemUpdateResult[] = [];
for (let i = 0; i < items.length; i++) {
// you need to use .then syntax here as otherwise the application will stop and await the result
batchedSP.web.lists
.getByTitle(this.LIBRARY_NAME)
.items
.getById(items[i].Id)
.update({ Title: `${items[i].Name}-Updated` })
.then(r => res.push(r));
}
// Executes the batched calls
await execute();
// Results for all batched calls are available
for (let i = 0; i < res.length; i++) {
//If the result is successful update the item
//NOTE: This code is over simplified, you need to make sure the Id's match
const item = await res[i].item.select("Id, Title")<{ Id: number, Title: string }>();
items[i].Name = item.Title;
}
//Update the state which rerenders the component
this.setState({ items });
} catch (err) {
Logger.write(`${this.LOG_SOURCE} (_updateTitles) - ${JSON.stringify(err)} - `, LogLevel.Error);
}
}
}
Executar o exemplo
Inicie a amostra e adicione a Web Part ao workbench hospedado do SharePoint (/_layouts/15/workbench.aspx) para vê-lo em ação.
gulp serve --nobrowser
Você pode excluir os itens existentes selecionando o ícone de cesto de lixo ou pode adicionar novos itens colocando valores em ambos os campos e selecionando Adicionar.
Próximas etapas
A @pnp/sp biblioteca contém um grande intervalo de funcionalidades e extensibilidade. Para ver amostras, orientações e dicas sobre como usar e configurar a biblioteca, confira o Guia do Desenvolvedor.