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.
Observação
Esta página foi desabilitada, uma vez que a AnuglarJS v1 é muito antiga e não tem mais suporte para os mantenedores Angulares.
Se estiver familiarizado com o AngularJS, também use essa estrutura extremamente popular para compilar Web Parts do lado do cliente. Graças à modularidade, ela pode ser usada para tudo, de complexos Aplicativos de Página Única de exibição múltipla a componentes menores, como Web Parts. No passado, muitas organizações usaram o AngularJS para criar soluções do SharePoint. Este artigo mostra como usar o AngularJS para compilar uma Web Part do lado do cliente da Estrutura do SharePoint e estilizá-la usando o Office UI Fabric.
Durante este tutorial, você compila uma Web Part simples que gerencia itens Pendentes.
A origem da Web Part em funcionamento está disponível no GitHub em samples/angular-todo/.
Observação
Antes de seguir as etapas neste artigo, configure seu ambiente de desenvolvimento para criar soluções da Estrutura do SharePoint.
Criar um novo projeto
Crie uma nova pasta para seu projeto
md angular-todo
Navegue até a pasta do projeto:
cd angular-todo
Na pasta do projeto, execute o gerador Yeoman da Estrutura do SharePoint para estruturar um novo projeto da Estrutura do SharePoint:
yo @microsoft/sharepoint
Quando solicitado, defina valores da seguinte maneira:
- angular-todo como o nome da solução
- Usar a pasta atual como o local para colocar os arquivos
- Sem estrutura da Web do JavaScript como o ponto de partida para criar a Web Part
- Pendentes como o nome da Web Part
- Gerenciamento simples de tarefas pendentes como sua descrição da Web Part
- Uma vez concluída a estrutura, bloqueie a versão das dependências do projeto ao executar este comando:
npm shrinkwrap
Abra a pasta do projeto no seu editor de código. Neste tutorial, você usa o Visual Studio Code.
Adicionar o AngularJS
Neste tutorial, você carrega um AngularJS da CDN.
No editor de código, abra o arquivo config/config.json e nas externals
propriedades, adicione as seguintes linhas:
"angular": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.6/angular.min.js",
"globalName": "angular"
}
Adicionar declarações de tipo AngularJS para TypeScript
Como você está fazendo referência ao AngularJS no código da sua Web Part, também será necessário uso do AngularJS typings para TypeScript.
Para instalá-las, execute o seguinte na linha de comando:
npm install @types/angular --save-dev
Implementar aplicativo AngularJS
Com todos os pré-requisitos in-loco, você pode começar pela implementação do aplicativo AngularJS de exemplo. Como ele é formado por vários arquivos, crie uma pasta separada para ele chamada aplicativo.
Implementar serviço de dados Pendentes
Na pasta aplicativo recém-criada, crie um novo arquivo chamado DataService.ts. No arquivo, cole o seguinte código:
export interface ITodo {
id: number;
title: string;
done: boolean;
}
export interface IDataService {
getTodos(hideFinishedTasks: boolean): angular.IPromise<ITodo[]>;
addTodo(todo: string): angular.IPromise<{}>;
deleteTodo(todo: ITodo): angular.IPromise<{}>;
setTodoStatus(todo: ITodo, done: boolean): angular.IPromise<{}>;
}
export default class DataService implements IDataService {
public static $inject: string[] = ['$q'];
private items: ITodo[] = [
{
id: 1,
title: 'Prepare demo Web Part',
done: true
},
{
id: 2,
title: 'Show demo',
done: false
},
{
id: 3,
title: 'Share code',
done: false
}
];
private nextId: number = 4;
constructor(private $q: angular.IQService) { }
public getTodos(hideFinishedTasks: boolean): angular.IPromise<ITodo[]> {
const deferred: angular.IDeferred<ITodo[]> = this.$q.defer();
const todos: ITodo[] = [];
for (let i: number = 0; i < this.items.length; i++) {
if (hideFinishedTasks && this.items[i].done) {
continue;
}
todos.push(this.items[i]);
}
deferred.resolve(todos);
return deferred.promise;
}
public addTodo(todo: string): angular.IPromise<{}> {
const deferred: angular.IDeferred<{}> = this.$q.defer();
this.items.push({
id: this.nextId++,
title: todo,
done: false
});
deferred.resolve();
return deferred.promise;
}
public deleteTodo(todo: ITodo): angular.IPromise<{}> {
const deferred: angular.IDeferred<{}> = this.$q.defer();
let pos: number = -1;
for (let i: number = 0; i < this.items.length; i++) {
if (this.items[i].id === todo.id) {
pos = i;
break;
}
}
if (pos > -1) {
this.items.splice(pos, 1);
deferred.resolve();
}
else {
deferred.reject();
}
return deferred.promise;
}
public setTodoStatus(todo: ITodo, done: boolean): angular.IPromise<{}> {
const deferred: angular.IDeferred<{}> = this.$q.defer();
for (let i: number = 0; i < this.items.length; i++) {
if (this.items[i].id === todo.id) {
this.items[i].done = done;
}
}
deferred.resolve();
return deferred.promise;
}
}
No trecho de código anterior, você pode implementar três tipos:
- A interface ITodo, que representa um item To Do no aplicativo.
- A interface IDataService, que define a assinatura do serviço de dados.
- A classe DataService, que é responsável por recuperar e manipular os itens Pendentes.
O serviço de dados implementa métodos simples para adicionar e modificar itens Pendentes. Embora as operações sejam instantâneas, por questões de consistência, cada função CRUD retorna uma promessa.
Neste tutorial, os itens Pendentes são armazenados na memória. No entanto, você poderia estender facilmente a solução para armazenar itens em uma lista do SharePoint e usar o serviço de dados para se comunicar com o SharePoint usando sua API REST.
Implementar o controlador
Em seguida, implemente o controlador que facilita a comunicação entre o modo de exibição e o serviço de dados.
Na pasta aplicativo, crie um novo arquivo chamado HomeController.ts e cole o seguinte código:
import { IDataService, ITodo } from './DataService';
export default class HomeController {
public isLoading: boolean = false;
public newItem: string = null;
public newToDoActive: boolean = false;
public todoCollection: any[] = [];
private hideFinishedTasks: boolean = false;
public static $inject: string[] = ['DataService', '$window', '$rootScope'];
constructor(private dataService: IDataService, private $window: angular.IWindowService, private $rootScope: angular.IRootScopeService) {
const vm: HomeController = this;
this.init();
}
private init(hideFinishedTasks?: boolean): void {
this.hideFinishedTasks = hideFinishedTasks;
this.loadTodos();
}
private loadTodos(): void {
const vm: HomeController = this;
this.isLoading = true;
this.dataService.getTodos(vm.hideFinishedTasks)
.then((todos: ITodo[]): void => {
vm.todoCollection = todos;
})
.finally((): void => {
vm.isLoading = false;
});
}
public todoKeyDown($event: any): void {
if ($event.keyCode === 13 && this.newItem.length > 0) {
$event.preventDefault();
this.todoCollection.unshift({ id: -1, title: this.newItem, done: false });
const vm: HomeController = this;
this.dataService.addTodo(this.newItem)
.then((): void => {
this.newItem = null;
this.dataService.getTodos(vm.hideFinishedTasks)
.then((todos: any[]): void => {
this.todoCollection = todos;
});
});
}
}
public deleteTodo(todo: ITodo): void {
if (this.$window.confirm('Are you sure you want to delete this todo item?')) {
let index: number = -1;
for (let i: number = 0; i < this.todoCollection.length; i++) {
if (this.todoCollection[i].id === todo.id) {
index = i;
break;
}
}
if (index > -1) {
this.todoCollection.splice(index, 1);
}
const vm: HomeController = this;
this.dataService.deleteTodo(todo)
.then((): void => {
this.dataService.getTodos(vm.hideFinishedTasks)
.then((todos: any[]): void => {
this.todoCollection = todos;
});
});
}
}
public completeTodo(todo: ITodo): void {
todo.done = true;
const vm: HomeController = this;
this.dataService.setTodoStatus(todo, true)
.then((): void => {
this.dataService.getTodos(vm.hideFinishedTasks)
.then((todos: any[]): void => {
this.todoCollection = todos;
});
});
}
public undoTodo(todo: ITodo): void {
todo.done = false;
const vm: HomeController = this;
this.dataService.setTodoStatus(todo, false)
.then((): void => {
this.dataService.getTodos(vm.hideFinishedTasks)
.then((todos: any[]): void => {
this.todoCollection = todos;
});
});
}
}
Inicie carregando o serviço de dados previamente implementado. O controlador precisa do serviço para obter a lista de itens e modifica os itens conforme solicitado pelo usuário. Usando a injeção de dependência do AngularJS, o serviço é inserido no controlador. O controlador implementa várias funções que são expostas ao modelo de exibição e são chamadas no modelo. Ao usar essas funções, os usuários podem adicionar novos itens, marcar itens como concluídos, Pendentes ou excluir itens.
Implemente o módulo principal
Com o serviço de dados e o controlador preparado, defina o módulo principal do seu aplicativo e registre o serviço de dados e o controlador com ele.
Na pasta aplicativo, crie um novo arquivo chamado app-module.ts e cole o seguinte conteúdo:
import * as angular from 'angular';
import HomeController from './HomeController';
import DataService from './DataService';
const todoapp: angular.IModule = angular.module('todoapp', []);
todoapp
.controller('HomeController', HomeController)
.service('DataService', DataService);
Comece fazendo referência ao AngularJS e carregando o controlador e o serviço de dados previamente implementados. Em seguida, defina o módulo do aplicativo. Por fim, registre o controlador e o serviço de dados com seu aplicativo.
Pronto. Você criou um aplicativo AngularJS para a Web Part. Nas etapas a seguir, você registra o aplicativo AngularJS na Web Part e o torna configurável usando as propriedades da Web Part.
Registre o aplicativo AngularJS na Web Part
A próxima etapa é adicionar o aplicativo AngularJS à Web Part.
No editor de código, abra o arquivo ToDoWebPart.ts.
Antes da declaração de classe, adicione as seguintes linhas:
import * as angular from 'angular'; import './app/app-module';
Isso permite carregar uma referência para o AngularJS e seu aplicativo, ambas necessárias para a inicialização do aplicativo AngularJS.
Altere a função render() da Web Part para:
public render(): void { if (this.renderedOnce === false) { this.domElement.innerHTML = ` <div class="${styles.toDo}"> <div data-ng-controller="HomeController as vm"> <div class="${styles.loading}" ng-show="vm.isLoading"> <div class="${styles.spinner}"> <div class="${styles.spinnerCircle} ${styles.spinnerLarge}"></div> <div class="${styles.spinnerLabel}">Loading...</div> </div> </div> <div ng-show="vm.isLoading === false"> <div class="${styles.textField} ${styles.underlined}" ng-class="{'${styles.isActive}': vm.newToDoActive}"> <label for="newToDo" class="${styles.label}">New to do:</label> <input type="text" label="New to do:" id="newToDo" value="" class="${styles.field}" aria-invalid="false" ng-model="vm.newItem" ng-keydown="vm.todoKeyDown($event)" ng-focus="vm.newToDoActive = true" ng-blur="vm.newToDoActive = false"> </div> </div> <div class="list" ng-show="vm.isLoading === false"> <div class="listSurface"> <div class="listPage"> <div class="listCell" ng-repeat="todo in vm.todoCollection" uif-item="todo" ng-class="{'${styles.done}': todo.done}"> <div class="${styles.listItem}"> <span class="${styles.listItemPrimaryText}">{{todo.title}}</span> <div class="${styles.listItemActions}"> <div class="${styles.listItemAction}" ng-click="vm.completeTodo(todo)" ng-show="todo.done === false"> <i class="${styles.icon} ${styles.iconCheckMark}"></i> </div> <div class="${styles.listItemAction}" ng-click="vm.undoTodo(todo)" ng-show="todo.done"> <i class="${styles.icon} ${styles.iconUndo}"></i> </div> <div class="${styles.listItemAction}" ng-click="vm.deleteTodo(todo)"> <i class="${styles.icon} ${styles.iconRecycleBin}"></i> </div> </div> </div> </div> </div> </div> </div> </div> </div>`; angular.bootstrap(this.domElement, ['todoapp']); } }
Primeiro, o código atribui o modelo do aplicativo diretamente ao elemento DOM da Web Part. No elemento raiz, especifique o nome do controlador que trata dos eventos e da associação de dados no modelo.
Inicialize seu aplicativo usando o nome todoapp utilizado anteriormente durante a declaração do módulo principal.
Ao usar a propriedade da Web Part
renderedOnce
, assegure-se de que seu aplicativo AngularJS seja inicializado apenas uma vez. Sem essa propriedade, se você tiver alterado uma das propriedades da Web Part, a funçãorender()
é invocada novamente voltando a inicializar o aplicativo AngularJS, o que resulta em um erro.Implemente os estilos CSS que você está usando no modelo. No editor de código, abra o arquivo ToDo.module.scss e substitua seu conteúdo por:
.toDo { .loading { margin: 0 auto; width: 6em; .spinner { display: inline-block; margin: 10px 0; @-webkit-keyframes spinnerSpin { 0% { -webkit-transform:rotate(0); transform:rotate(0); } 100% { -webkit-transform:rotate(360deg); transform:rotate(360deg); } } @keyframes spinnerSpin { 0% { -webkit-transform:rotate(0); transform:rotate(0); } 100% { -webkit-transform:rotate(360deg); transform:rotate(360deg); } } .spinnerCircle { margin: auto; box-sizing: border-box; border-radius: 50%; width: 100%; height: 100%; border: 1.5px solid #c7e0f4; border-top-color: #0078d7; -webkit-animation: spinnerSpin 1.3s infinite cubic-bezier(.53, .21, .29, .67); animation: spinnerSpin 1.3s infinite cubic-bezier(.53, .21, .29, .67); &.spinnerLarge { width: 28px; height: 28px; } } .spinnerLabel { color: #0078d7; margin-top: 10px; text-align: center; } } } .label { font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; font-weight: 400; box-sizing: border-box; margin: 0; padding: 0; box-shadow: none; color: #333333; box-sizing: border-box; display: block; padding: 5px 0 } .textField { font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; box-sizing: border-box; margin: 0; padding: 0; box-shadow: none; color: #333333; font-size: 14px; font-weight: 400; margin-bottom: 8px; &.underlined { border-bottom: 1px solid #c8c8c8; display: table; width: 100%; &:hover { border-color: #767676; } &.isActive, &:active { border-color: #0078d7; } .field { border: 0; display: table-cell; padding-top: 8px; padding-bottom: 3px } } .label { padding-right: 0; padding-left: 12px; margin-right: 8px; font-size: 14px; display: table-cell; vertical-align: top; padding-top: 9px; height: 32px; width: 1%; white-space: nowrap; font-weight: 400; } .field { text-align: left; float: left; box-sizing: border-box; margin: 0; padding: 0; box-shadow: none; font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; border: 1px solid #c8c8c8; border-radius: 0; font-weight: 400; font-size: 14px; color: #333333; height: 32px; padding: 0 12px 0 12px; width: 100%; outline: 0; text-overflow: ellipsis; } .field:hover { border-color: #767676; } } .listItem { font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; font-weight: 400; box-sizing: border-box; margin: 0; padding: 0; box-shadow: none; padding: 9px 28px 3px; position: relative; display: block; &::before { display: table; content: ""; line-height: 0; } &::after { display: table; content: ""; line-height: 0; clear: both; } .listItemPrimaryText { font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; font-size: 21px; font-weight: 100; padding-right: 80px; position: relative; top: -4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; } .listItemActions { max-width: 80px; position: absolute; right: 30px; text-align: right; top: 10px; .listItemAction { color: #a6a6a6; display: inline-block; font-size: 15px; position: relative; text-align: center; top: 3px; cursor: pointer; height: 16px; width: 16px; &:hover { color: #666666; outline: 1px solid transparent; } .icon { vertical-align: top; } } } } .done { text-decoration: line-through; } .icon { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; display: inline-block; font-family: FabricMDL2Icons; font-style: normal; font-weight: 400; speak: none; &.iconCheckMark::before { content: "\E73E"; } &.iconUndo::before { content: "\E7A7"; } &.iconRecycleBin::before { content: "\EF87"; } } }
Execute o seguinte comando para confirmar que tudo está funcionando conforme o esperado:
gulp serve
No navegador, você deverá ver a Web Part Pendentes mostrando itens Pendentes.
Torne a Web Part configurável
Neste ponto, a Web Part Pendentes mostra uma lista fixa de itens Pendentes. Em seguida, você estende a Web Part com uma opção de configuração para permitir que os usuários escolham se eles querem ver itens marcados como concluídos ou não.
Adicione propriedade no manifesto da Web Part
Comece adicionando uma propriedade de configuração ao manifesto da Web Part.
No editor de código, abra o arquivo ToDoWebPart.manifest.json. Na seção preconfiguredEntries
, navegue até a matriz properties
e substitua a propriedade description
pela seguinte linha:
"hideFinishedTasks": false
Atualizar a assinatura da interface de propriedades da Web Part
Em seguida, atualize a assinatura da interface de propriedades da Web Part.
No editor de código aberto, abra o arquivo ToDoWebPart.ts e atualize a interface IToDoWebPartProps
para o seguinte:
export interface IToDoWebPartProps {
hideFinishedTasks: boolean;
}
Adicionar a propriedade ao painel de propriedades da Web Part
Para permitir que os usuários definam o valor de sua propriedade recém-adicionada, você tem que expô-lo no painel de propriedades da Web Part.
No editor de código, abra o arquivo ToDoWebPart.ts.
Na primeira
import
instrução, substitua porPropertyPaneTextField
PropertyPaneToggle
:import { BaseClientSideWebPart, IPropertyPaneConfiguration, PropertyPaneToggle } from '@microsoft/sp-webpart-base';
Altere a implementação da função
propertyPaneSettings
para:protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { return { pages: [ { header: { description: strings.PropertyPaneDescription }, groups: [ { groupName: strings.ViewGroupName, groupFields: [ PropertyPaneToggle('hideFinishedTasks', { label: strings.HideFinishedTasksFieldLabel }) ] } ] } ] }; }
Para corrigir as referências da cadeia de caracteres ausente, primeiro, é necessário alterar a assinatura de suas cadeias de caracteres. No editor de código, abra o arquivo loc/mystrings.d.ts e altere seu conteúdo para:
declare interface IToDoWebPartStrings { PropertyPaneDescription: string; ViewGroupName: string; HideFinishedTasksFieldLabel: string; } declare module 'ToDoWebPartStrings' { const strings: IToDoWebPartStrings; export = strings; }
Forneça os valores reais para as cadeias de caracteres recentemente definidas. No editor de código, abra o arquivo loc/en-us.js e altere seu conteúdo para:
define([], function() { return { "PropertyPaneDescription": "Manage configuration of the To do web part", "ViewGroupName": "View", "HideFinishedTasksFieldLabel": "Hide finished tasks" } });
Execute o seguinte comando para confirmar que tudo está funcionando conforme o esperado:
gulp serve
No painel de propriedades da Web Part, você verá um botão de alternância para sua propriedade definida recentemente.
Neste ponto, selecionar o botão de alternância não afeta a Web Part, já que ela ainda não foi conectada ao AngularJS. Isso é feito na próxima etapa.
Torne configurável o aplicativo AngularJS usando as propriedades da Web Part
Na etapa anterior, você definiu uma propriedade da Web Part que permite aos usuários escolher se desejam mostrar tarefas concluídas ou não. Em seguida, você passa o valor selecionado pelo usuário para o aplicativo AngularJS para que ele carregue itens de forma adequada.
Transmitir o evento do AngularJS em uma alteração da propriedade da Web Part
No editor de código, abra o arquivo ToDoWebPart.ts. Logo acima do método
render()
, adicione a seguinte linha:private $injector: angular.auto.IInjectorService;
Altere a função
render()
da Web Part para o seguinte:public render(): void { if (this.renderedOnce === false) { this.domElement.innerHTML = ` <div class="${styles.toDo}"> <div data-ng-controller="HomeController as vm"> <div class="${styles.loading}" ng-show="vm.isLoading"> <div class="${styles.spinner}"> <div class="${styles.spinnerCircle} ${styles.spinnerLarge}"></div> <div class="${styles.spinnerLabel}">Loading...</div> </div> </div> <div ng-show="vm.isLoading === false"> <div class="${styles.textField} ${styles.underlined}" ng-class="{'${styles.isActive}': vm.newToDoActive}"> <label for="newToDo" class="${styles.label}">New to do:</label> <input type="text" label="New to do:" id="newToDo" value="" class="${styles.field}" aria-invalid="false" ng-model="vm.newItem" ng-keydown="vm.todoKeyDown($event)" ng-focus="vm.newToDoActive = true" ng-blur="vm.newToDoActive = false"> </div> </div> <div class="list" ng-show="vm.isLoading === false"> <div class="listSurface"> <div class="listPage"> <div class="listCell" ng-repeat="todo in vm.todoCollection" uif-item="todo" ng-class="{'${styles.done}': todo.done}"> <div class="${styles.listItem}"> <span class="${styles.listItemPrimaryText}">{{todo.title}}</span> <div class="${styles.listItemActions}"> <div class="${styles.listItemAction}" ng-click="vm.completeTodo(todo)" ng-show="todo.done === false"> <i class="${styles.icon} ${styles.iconCheckMark}"></i> </div> <div class="${styles.listItemAction}" ng-click="vm.undoTodo(todo)" ng-show="todo.done"> <i class="${styles.icon} ${styles.iconUndo}"></i> </div> <div class="${styles.listItemAction}" ng-click="vm.deleteTodo(todo)"> <i class="${styles.icon} ${styles.iconRecycleBin}"></i> </div> </div> </div> </div> </div> </div> </div> </div> </div>`; this.$injector = angular.bootstrap(this.domElement, ['todoapp']); } this.$injector.get('$rootScope').$broadcast('configurationChanged', { hideFinishedTasks: this.properties.hideFinishedTasks }); }
No código de exemplo anterior, sempre que a propriedade da Web Part é alterada, ela transmite um evento do AngularJS para o qual o aplicativo AngularJS se inscreve. Quando o evento for recebido pelo aplicativo AngularJS, então ele manipula o evento da forma mais adequada.
Inscreva-se no evento de alteração da propriedade da Web Part no AngularJS
No editor de código, abra o arquivo app/HomeController.ts. Estenda o construtor da seguinte maneira:
constructor(private dataService: IDataService, private $window: angular.IWindowService, private $rootScope: angular.IRootScopeService) { const vm: HomeController = this; this.init(); $rootScope.$on('configurationChanged', (event: angular.IAngularEvent, args: { hideFinishedTasks: boolean }): void => { vm.init(args.hideFinishedTasks); }); }
Verifique se o aplicativo AngularJS está funcionando como esperado e responda corretamente à propriedade alterada ao executar o seguinte na linha de comando:
gulp serve
Se você alternar o valor da propriedade Ocultar tarefas concluídas, a Web Part deverá mostrar ou ocultar as tarefas concluídas de acordo com a opção escolhida.