Criar um suplemento de Project que usa REST com um serviço OData local do Project Server
Este artigo descreve como criar um suplemento do painel de tarefas para Project Professional que compara os dados de custo e trabalho no projeto ativo com as médias de todos os projetos na instância atual do Project Web App. O suplemento utiliza REST com a biblioteca jQuery para aceder ao serviço de relatórios ProjectData OData no Project Server.
O código deste artigo é baseado em um exemplo desenvolvido por Saurabh Sanghvi e Arvind Iyer, da Microsoft Corporation.
Pré-requisitos
Seguem-se os pré-requisitos para criar um suplemento do painel de tarefas do Project que lê o serviço ProjectData de uma instância do Project Web App numa instalação no local do Project Server.
Project Professional é necessário para ligar ao Project Web App. O computador de desenvolvimento tem de ter Project Professional instalado para ativar a depuração F5 com o Visual Studio.
Observação
Project Standard também pode alojar suplementos do painel de tarefas, mas não pode iniciar sessão no Project Web App.
O Visual Studio 2015 com as Ferramentas de Programação do Office para Visual Studio inclui modelos para criar Suplementos do Office e do SharePoint. Certifique-se de que instalou a versão mais recente das Ferramentas de Programação do Office; consulte a secção Ferramentas dos Suplementos do Office e transferências do SharePoint.
Os procedimentos e exemplos de código neste artigo acedem ao serviço ProjectData do Project Server num domínio local. Os métodos jQuery neste artigo não funcionam com o Project na Web.
Verifique se o serviço ProjectData está acessível a partir do seu computador de desenvolvimento.
Procedimento 1. Verifique se o serviço ProjectData está acessível
Para permitir que o browser mostre diretamente os dados XML de uma consulta REST, desative a vista de leitura do feed. Para obter informações sobre como fazê-lo na Internet Explorer, consulte Procedimento 1, passo 4 em Feeds OData de Consulta para dados de relatórios do Project.
Consulte o serviço ProjectData com o browser com o seguinte URL:
http://ServerName /ProjectServerName /_api/ProjectData
. Por exemplo, se a instância do Project Web App forhttp://MyServer/pwa
, o browser mostra os seguintes resultados.<?xml version="1.0" encoding="utf-8"?> <service xml:base="http://myserver/pwa/_api/ProjectData/" xmlns="https://www.w3.org/2007/app" xmlns:atom="https://www.w3.org/2005/Atom"> <workspace> <atom:title>Default</atom:title> <collection href="Projects"> <atom:title>Projects</atom:title> </collection> <collection href="ProjectBaselines"> <atom:title>ProjectBaselines</atom:title> </collection> <!-- ... and 33 more collection elements --> </workspace> </service>
Poderá ter de fornecer as credenciais de rede para ver os resultados. Se o navegador exibir "Erro 403, acesso negado" você não tem permissão de logon para essa instância do Project Web App ou há um problema de rede que exige ajuda administrativa.
Utilizar o Visual Studio para criar um suplemento do painel de tarefas para o Project
As Ferramentas de Programação do Office para Visual Studio incluem um modelo para suplementos do painel de tarefas para o Project. Se criar uma solução com o nome HelloProjectOData, a solução contém os dois projetos seguintes do Visual Studio:
O projeto de suplemento usa o nome da solução. Inclui o ficheiro de manifesto apenas de suplemento para o suplemento e destina-se ao .NET Framework 4.5. O procedimento 3 mostra os passos para modificar o manifesto do suplemento HelloProjectOData .
O projeto Web tem o nome HelloProjectODataWeb. Ele inclui as páginas da Web, os arquivos JavaScript, os arquivos CSS, as imagens, as referências e os arquivos de configuração para o conteúdo Web no painel de tarefas. O projeto Web serve para o .NET Framework 4. O Procedimento 4 e o Procedimento 5 mostram como modificar os ficheiros no projeto Web para criar a funcionalidade do suplemento HelloProjectOData .
Procedimento 2. Criar o suplemento HelloProjectOData para o Project
Execute o Visual Studio 2015 como administrador e, em seguida, selecione Novo Projeto na página Início.
Na caixa de diálogo Novo Projeto, expanda os nós Modelos, Visual C#e Office/SharePoint e, em seguida, selecione Suplementos do Office. Selecione .NET Framework 4.5.2 na lista pendente arquitetura de destino na parte superior do painel central e, em seguida, selecione Suplemento do Office (ver a captura de ecrã seguinte).
Para colocar ambos os projetos do Visual Studio no mesmo diretório, selecione Criar diretório para solução e, em seguida, navegue para a localização pretendida.
No campo Nome , escrevaHelloProjectOData e, em seguida, selecione OK.
Figura 1. Criar um Suplemento do Office
Na caixa de diálogo Escolher o tipo de suplemento , selecione Painel de tarefas e selecioneSeguinte (ver a captura de ecrã seguinte).
Figura 2. Escolher o tipo de suplemento a criar
Na caixa de diálogo Escolher as aplicações anfitriãs, desmarque todas as caixas de marcar exceto a caixa de marcar do Project (veja a captura de ecrã seguinte) e selecione Concluir.
Figura 3. Choosing the host application
O Visual Studio cria o projeto HelloProjectOdata e o projeto HelloProjectODataWeb .
A pasta AddIn (ver a captura de ecrã seguinte) contém o ficheiro App.css para estilos CSS personalizados. Na subpasta Home, o arquivo Home.html contém referências para arquivos CSS e JavaScript que o suplemento usa, e o conteúdo HTML5 para o suplemento. Além disso, o arquivo Home.js é para o seu código JavaScript personalizado. A pasta Scripts inclui os arquivos da biblioteca jQuery. A subpasta Office inclui as bibliotecas JavaScript, como office.js e project-15.js, além das bibliotecas de linguagem para cadeias de caracteres padrão nos suplementos do Office. Na pasta Content, o arquivo Office.css contém os estilos padrão de todos os Suplementos do Office.
Figura 4. Ver os ficheiros de projeto Web predefinidos no Gerenciador de Soluções
O manifesto do projeto HelloProjectOData é o ficheiro HelloProjectOData.xml. Opcionalmente, você pode modificar o manifesto para adicionar uma descrição do suplemento, uma referência a um ícone, informações de linguagem adicionais e outras configurações. O Procedimento 3 simplesmente modifica o nome de exibição e a descrição do suplemento e adiciona um ícone.
Para obter mais informações sobre o manifesto, consulte Manifesto de Suplementos do Office e Referência de esquema para manifestos de Suplementos do Office.
Procedimento 3. Modificar o manifesto do suplemento
No Visual Studio, abra o arquivo HelloProjectOData.xml.
O nome de exibição padrão é o nome do projeto do Visual Studio ("HelloProjectOData"). Por exemplo, altere o valor predefinido do <elemento DisplayName> para "Hello ProjectData".
A descrição padrão também é "HelloProjectOData". Por exemplo, altere o valor padrão do elemento Description para "Testar consultas REST do serviço ProjectData".
Adicione um ícone a mostrar na lista pendente Suplementos do Office no separador PROJETO do friso. Você pode adicionar um arquivo de ícone na solução do Visual Studio ou usar uma URL para um ícone.
Os passos seguintes mostram como adicionar um ficheiro de ícone à solução visual Studio.
No Gerenciador de Soluções, aceda à pasta com o nome Imagens.
Para ser apresentado na lista pendente Suplementos do Office , o ícone tem de ter 32 x 32 píxeis. Utilize o seu próprio ícone 32 x 32; ou copie a imagem seguinte para um ficheiro com o nome NewIcon.pnge, em seguida, adicione esse ficheiro à
HelloProjectODataWeb\Images
pasta.Na HelloProjectOData.xml, adicione um <elemento IconUrl> abaixo do <elemento Description> , em que o valor do URL do ícone é o caminho relativo para o ficheiro de ícone 32x32. Por exemplo, adicione a seguinte linha:
<IconUrl DefaultValue="~remoteAppUrl/Images/NewIcon.png" />
. O ficheiro HelloProjectOData.xml contém agora o seguinte (o <seu valor de ID> será diferente):<?xml version="1.0" encoding="UTF-8"?> <OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="TaskPaneApp"> <!--IMPORTANT! Id must be unique for each add-in. If you copy this manifest ensure that you change this id to your own GUID. --> <Id>c512df8d-a1c5-4d74-8a34-d30f6bbcbd82</Id> <Version>1.0</Version> <ProviderName> [Provider name]</ProviderName> <DefaultLocale>en-US</DefaultLocale> <DisplayName DefaultValue="Hello ProjectData" /> <Description DefaultValue="Test REST queries of the ProjectData service"/> <IconUrl DefaultValue="~remoteAppUrl/Images/NewIcon.png" /> <SupportUrl DefaultValue="[Insert the URL of a page that provides support information for the app]" /> <Hosts> <Host Name="Project" /> </Hosts> <DefaultSettings> <SourceLocation DefaultValue="~remoteAppUrl/AddIn/Home/Home.html" /> </DefaultSettings> <Permissions>ReadWriteDocument</Permissions> </OfficeApp>
Criar o conteúdo HTML para o suplemento HelloProjectOData
O suplemento HelloProjectOData é um exemplo que inclui depuração e saída de erros; não se destina a utilização de produção. Antes de começar a codificar o conteúdo HTML, crie a IU e a experiência de utilizador para o suplemento e delinee as funções JavaScript que interagem com o código HTML. Para obter mais informações, consulte Diretrizes de estrutura para Suplementos do Office.
O painel de tarefas mostra o nome a apresentar do suplemento na parte superior, que é o valor do <elemento DisplayName> no manifesto. O elemento body no arquivo HelloProjectOData.html contém outros elementos de interface do usuário, da seguinte maneira:
Um subtítulo indica a funcionalidade geral ou o tipo de operação, por exemplo, CONSULTA REST ODATA.
O botão Obter Ponto Final do ProjectData chama a
setOdataUrl
função para obter o ponto final do serviço ProjectData e apresentá-lo numa caixa de texto. Se o projeto não estiver conectado ao Project Web App, o suplemento chama um identificador de erro para exibir uma mensagem de erro pop-up.O botão Comparar Todos os Projetos está desativado até que o suplemento obtenha um ponto final OData válido. Quando seleciona o botão, chama a
retrieveOData
função , que utiliza uma consulta REST para obter dados de trabalho e custos do projeto do serviço ProjectData .Uma tabela exibe os valores médios de custo do projeto, custo real, trabalho e porcentagem concluída. A tabela também compara os valores atuais do projeto ativo com a média. Se o valor atual for maior que a média de todos os projetos, será exibido em vermelho. Se o valor atual for menor que a média, será exibido em verde. Se o valor atual não estiver disponível, a tabela apresenta um NA azul.
A
retrieveOData
função chama aparseODataResult
função, que calcula e apresenta valores para a tabela.Observação
Neste exemplo, os dados de custo e trabalho do projeto ativo derivam dos valores publicados. Se alterar valores no Project, o serviço ProjectData não tem as alterações até que o projeto seja publicado.
Procedimento 4. Criar o conteúdo HTML
No elemento principal do ficheiro Home.html, adicione quaisquer elementos de ligação adicionais para ficheiros CSS utilizados pelo suplemento. O modelo de projeto do Visual Studio inclui um link para o arquivo App.css que você pode usar para os estilos CSS personalizados.
Adicione quaisquer elementos de script adicionais para bibliotecas JavaScript que o suplemento utiliza. O modelo de projeto inclui ligações para os ficheiros jQuery- [versão].js, office.js e MicrosoftAjax.js na pasta Scripts .
Observação
Antes de implementar o suplemento, altere a referência office.js e a referência jQuery para a referência da rede de entrega de conteúdos (CDN). A referência CDN fornece a versão mais recente e melhora o desempenho.
O suplemento HelloProjectOData também utiliza um ficheiro SurfaceErrors.js , que apresenta erros numa mensagem de pop-up. Copie o código da secçãoSurfaceErrors.js ficheiro deste artigo para a pasta Scripts\Office do projeto HelloProjectODataWeb como um novo ficheiro com o nome SurfaceErrors.js.
Segue-se o código HTML atualizado para o elemento principal , com a linha adicional para o ficheiro SurfaceErrors.js .
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> <title>Test ProjectData Service</title> <link rel="stylesheet" type="text/css" href="../Content/Office.css" /> <!-- Add your CSS styles to the following file. --> <link rel="stylesheet" type="text/css" href="../Content/App.css" /> <!-- Use the CDN reference to the mini-version of jQuery when deploying your add-in. --> <!--<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.min.js"></script> --> <script src="../Scripts/jquery-1.7.1.js"></script> <!-- Use the CDN reference to office.js when deploying your add-in. --> <!--<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>--> <!-- Use the local script references for Office.js to enable offline debugging --> <script src="../Scripts/Office/1.0/MicrosoftAjax.js"></script> <script src="../Scripts/Office/1.0/Office.js"></script> <!-- Add your JavaScript to the following files. --> <script src="../Scripts/HelloProjectOData.js"></script> <script src="../Scripts/SurfaceErrors.js"></script> </head> <body> <!-- See the code in Step 3. --> </body> </html>
No elemento do corpo , elimine o código existente do modelo e, em seguida, adicione o código para a interface de utilizador. Se um elemento deve ser preenchido com os dados ou manipulado por uma instrução jQuery, deve incluir um atributo id exclusivo. No código seguinte, os atributos de ID para os elementos de botão, span e td (definição de célula de tabela) que as funções jQuery utilizam são apresentados a negrito.
O HTML seguinte adiciona uma imagem gráfica (NewLogo.png), que pode ser um logótipo da empresa.
<body> <div id="SectionContent"> <div id="odataQueries"> ODATA REST QUERY </div> <div id="odataInfo"> <button class="button-wide" onclick="setOdataUrl()">Get ProjectData Endpoint</button> <br /><br /> <span class="rest" id="projectDataEndPoint">Endpoint of the <strong>ProjectData</strong> service</span> <br /> </div> <div id="compareProjectData"> <button class="button-wide" disabled="disabled" id="compareProjects" onclick="retrieveOData()">Compare All Projects</button> <br /> </div> </div> <div id="corpInfo"> <table class="infoTable" aria-readonly="True" style="width: 100%;"> <tr> <td class="heading_leftCol"></td> <td class="heading_midCol"><strong>Average</strong></td> <td class="heading_rightCol"><strong>Current</strong></td> </tr> <tr> <td class="row_leftCol"><strong>Project Cost</strong></td> <td class="row_midCol" id="AverageProjectCost">&nbsp;</td> <td class="row_rightCol" id="CurrentProjectCost">&nbsp;</td> </tr> <tr> <td class="row_leftCol"><strong>Project Actual Cost</strong></td> <td class="row_midCol" id="AverageProjectActualCost">&nbsp;</td> <td class="row_rightCol" id="CurrentProjectActualCost">&nbsp;</td> </tr> <tr> <td class="row_leftCol"><strong>Project Work</strong></td> <td class="row_midCol" id="AverageProjectWork">&nbsp;</td> <td class="row_rightCol" id="CurrentProjectWork">&nbsp;</td> </tr> <tr> <td class="row_leftCol"><strong>Project % Complete</strong></td> <td class="row_midCol" id="AverageProjectPercentComplete">&nbsp;</td> <td class="row_rightCol" id="CurrentProjectPercentComplete">&nbsp;</td> </tr> </table> </div> <img alt="Corporation" class="logo" src="../../images/NewLogo.png" /> <br /> <textarea id="odataText" rows="12" cols="40"></textarea> </body>
Criar o código JavaScript para o suplemento
O modelo para um suplemento do painel de tarefas do Project inclui código de inicialização predefinido que foi concebido para demonstrar ações básicas de obtenção e definição de dados num documento para um suplemento do Office que utiliza as APIs Comuns. Uma vez que o Project não suporta ações que escrevem no projeto ativo e o suplemento HelloProjectOData não utiliza o getSelectedDataAsync
método , pode eliminar o script na Office.initialize
função e eliminar a setData
função e getData
a função no ficheiro de HelloProjectOData.js predefinido.
O JavaScript inclui constantes globais para a consulta REST e variáveis globais que são usadas em várias funções. O botão Obter Ponto Final do ProjectData chama a setOdataUrl
função, que inicializa as variáveis globais e determina se o Project está ligado ao Project Web App.
O resto do ficheiro de HelloProjectOData.js inclui duas funções: a retrieveOData
função é chamada quando o utilizador seleciona Comparar Todos os Projetos; e a parseODataResult
função calcula as médias e, em seguida, preenche a tabela de comparação com valores formatados para cores e unidades.
Procedimento 5. Criar o código JavaScript
Elimine todo o código no ficheiro de HelloProjectOData.js predefinido e, em seguida, adicione as variáveis e
Office.initialize
a função globais. Os nomes de variáveis que são todos maiúsculas implicam que são constantes; posteriormente, são utilizadas com a_pwa
variável para criar a consulta REST neste exemplo.let PROJDATA = "/_api/ProjectData"; let PROJQUERY = "/Projects?"; let QUERY_FILTER = "$filter=ProjectName ne 'Timesheet Administrative Work Items'"; let QUERY_SELECT1 = "&$select=ProjectId, ProjectName"; let QUERY_SELECT2 = ", ProjectCost, ProjectWork, ProjectPercentCompleted, ProjectActualCost"; let _pwa; // URL of Project Web App. let _projectUid; // GUID of the active project. let _docUrl; // Path of the project document. let _odataUrl = ""; // URL of the OData service: http[s]://ServerName /ProjectServerName /_api/ProjectData // Ensure the Office.js library is loaded. Office.onReady(function() { // Office is ready. $(document).ready(function () { // The document is ready. }); });
Adicionar
setOdataUrl
e funções relacionadas. AsetOdataUrl
função chamagetProjectGuid
egetDocumentUrl
inicializa as variáveis globais. No método getProjectFieldAsync, a função anónima para o parâmetro de chamada de retorno ativa o botão Comparar Todos os Projetos ao utilizar oremoveAttr
método na biblioteca jQuery e, em seguida, apresenta o URL do serviço ProjectData . Se o Project não estiver conectado ao Project Web App, a função gera um erro e exibe uma mensagem de erro pop-up. O ficheiro SurfaceErrors.js inclui athrowError
função .Observação
Se executar o Visual Studio no computador do Project Server, para utilizar a depuração F5 , anule o comentário do código após a linha que inicializa a
_pwa
variável global. Para ativar a utilização do método jQueryajax
ao depurar no computador do Project Server, tem de definir olocalhost
valor para o URL do PWA. Se executar o Visual Studio num computador remoto, olocalhost
URL não é necessário. Before you deploy the add-in, comment out that code.function setOdataUrl() { Office.context.document.getProjectFieldAsync( Office.ProjectProjectFields.ProjectServerUrl, function (asyncResult) { if (asyncResult.status == Office.AsyncResultStatus.Succeeded) { _pwa = String(asyncResult.value.fieldValue); // If you debug with Visual Studio on a local Project Server computer, // uncomment the following lines to use the localhost URL. //let localhost = location.host.split(":", 1); //let pwaStartPosition = _pwa.lastIndexOf("/"); //let pwaLength = _pwa.length - pwaStartPosition; //let pwaName = _pwa.substr(pwaStartPosition, pwaLength); //_pwa = location.protocol + "//" + localhost + pwaName; if (_pwa.substring(0, 4) == "http") { _odataUrl = _pwa + PROJDATA; $("#compareProjects").removeAttr("disabled"); getProjectGuid(); } else { _odataUrl = "No connection!"; throwError(_odataUrl, "You are not connected to Project Web App."); } getDocumentUrl(); $("#projectDataEndPoint").text(_odataUrl); } else { throwError(asyncResult.error.name, asyncResult.error.message); } } ); } // Get the GUID of the active project. function getProjectGuid() { Office.context.document.getProjectFieldAsync( Office.ProjectProjectFields.GUID, function (asyncResult) { if (asyncResult.status == Office.AsyncResultStatus.Succeeded) { _projectUid = asyncResult.value.fieldValue; } else { throwError(asyncResult.error.name, asyncResult.error.message); } } ); } // Get the path of the project in Project web app, which is in the form <>\ProjectName . function getDocumentUrl() { _docUrl = "Document path:\r\n" + Office.context.document.url; }
Adicione a
retrieveOData
função, que concatena valores para a consulta REST e, em seguida, chama aajax
função no jQuery para obter os dados pedidos do serviço ProjectData . Asupport.cors
variável permite a partilha de recursos entre origens (CORS) com aajax
função . Se asupport.cors
instrução estiver em falta ou estiver definida comofalse
, aajax
função devolve um erro Sem transporte .Observação
O código seguinte funciona com uma instalação no local do Project Server. Para o Project na Web, pode utilizar o OAuth para autenticação baseada em tokens. For more information, see Addressing same-origin policy limitations in Office Add-ins.
ajax
Na chamada, pode utilizar o parâmetro headers ou o parâmetro beforeSend. O parâmetro completo é uma função anónima para que esteja no mesmo âmbito que as variáveis emretrieveOData
. A função para o parâmetro completo apresenta resultadosodataText
no controlo e também chama oparseODataResult
método para analisar e apresentar a resposta JSON. O parâmetro de erro especifica a função nomeadagetProjectDataErrorHandler
, que escreve uma mensagem de erro noodataText
controlo e também utiliza athrowError
função para apresentar uma mensagem de pop-up.// Functions to get and parse the Project Server reporting data./ // Get data about all projects on Project Server, // by using a REST query with the ajax method in jQuery. function retrieveOData() { let restUrl = _odataUrl + PROJQUERY + QUERY_FILTER + QUERY_SELECT1 + QUERY_SELECT2; let accept = "application/json; odata=verbose"; accept.toLocaleLowerCase(); // Enable cross-origin scripting (required by jQuery 1.5 and later). // This does not work with Project on the web. $.support.cors = true; $.ajax({ url: restUrl, type: "GET", contentType: "application/json", data: "", // Empty string for the optional data. //headers: { "Accept": accept }, beforeSend: function (xhr) { xhr.setRequestHeader("ACCEPT", accept); }, complete: function (xhr, textStatus) { // Create a message to display in the text box. let message = "\r\ntextStatus: " + textStatus + "\r\nContentType: " + xhr.getResponseHeader("Content-Type") + "\r\nStatus: " + xhr.status + "\r\nResponseText:\r\n" + xhr.responseText; // xhr.responseText is the result from an XmlHttpRequest, which // contains the JSON response from the OData service. parseODataResult(xhr.responseText, _projectUid); // Write the document name, response header, status, and JSON to the odataText control. $("#odataText").text(_docUrl); $("#odataText").append("\r\nREST query:\r\n" + restUrl); $("#odataText").append(message); if (xhr.status != 200 && xhr.status != 1223 && xhr.status != 201) { $("#odataInfo").append("<div>" + htmlEncode(restUrl) + "</div>"); } }, error: getProjectDataErrorHandler }); } function getProjectDataErrorHandler(data, errorCode, errorMessage) { $("#odataText").text("Error code: " + errorCode + "\r\nError message: \r\n" + errorMessage); throwError(errorCode, errorMessage); }
Adicione a
parseODataResult
função , que anula a serialização e processa a resposta JSON do serviço OData. AparseODataResult
função calcula os valores médios dos dados de custo e trabalho para uma precisão de uma ou duas casas decimais, formata valores com a cor correta e adiciona uma unidade ( $, hrs ou %) e, em seguida, apresenta os valores em células de tabela especificadas.Se o GUID do projeto ativo corresponder ao
ProjectId
valor, amyProjectIndex
variável será definida para o índice do projeto. SemyProjectIndex
indicar que o projeto ativo está publicado no Project Server, oparseODataResult
método formatará e apresentará os dados de custo e trabalho desse projeto. If the active project is not published, values for the active project are displayed as a blue NA.// Calculate the average values of actual cost, cost, work, and percent complete // for all projects, and compare with the values for the current project. function parseODataResult(oDataResult, currentProjectGuid) { // Deserialize the JSON string into a JavaScript object. let res = Sys.Serialization.JavaScriptSerializer.deserialize(oDataResult); let len = res.d.results.length; let projActualCost = 0; let projCost = 0; let projWork = 0; let projPercentCompleted = 0; let myProjectIndex = -1; for (i = 0; i < len; i++) { // If the current project GUID matches the GUID from the OData query, // store the project index. if (currentProjectGuid.toLocaleLowerCase() == res.d.results[i].ProjectId) { myProjectIndex = i; } projCost += Number(res.d.results[i].ProjectCost); projWork += Number(res.d.results[i].ProjectWork); projActualCost += Number(res.d.results[i].ProjectActualCost); projPercentCompleted += Number(res.d.results[i].ProjectPercentCompleted); } let avgProjCost = projCost / len; let avgProjWork = projWork / len; let avgProjActualCost = projActualCost / len; let avgProjPercentCompleted = projPercentCompleted / len; // Round off cost to two decimal places, and round off other values to one decimal place. avgProjCost = avgProjCost.toFixed(2); avgProjWork = avgProjWork.toFixed(1); avgProjActualCost = avgProjActualCost.toFixed(2); avgProjPercentCompleted = avgProjPercentCompleted.toFixed(1); // Display averages in the table, with the correct units. document.getElementById("AverageProjectCost").innerHTML = "$" + avgProjCost; document.getElementById("AverageProjectActualCost").innerHTML = "$" + avgProjActualCost; document.getElementById("AverageProjectWork").innerHTML = avgProjWork + " hrs"; document.getElementById("AverageProjectPercentComplete").innerHTML = avgProjPercentCompleted + "%"; // Calculate and display values for the current project. if (myProjectIndex != -1) { let myProjCost = Number(res.d.results[myProjectIndex].ProjectCost); let myProjWork = Number(res.d.results[myProjectIndex].ProjectWork); let myProjActualCost = Number(res.d.results[myProjectIndex].ProjectActualCost); let myProjPercentCompleted = Number(res.d.results[myProjectIndex].ProjectPercentCompleted); myProjCost = myProjCost.toFixed(2); myProjWork = myProjWork.toFixed(1); myProjActualCost = myProjActualCost.toFixed(2); myProjPercentCompleted = myProjPercentCompleted.toFixed(1); document.getElementById("CurrentProjectCost").innerHTML = "$" + myProjCost; if (Number(myProjCost) <= Number(avgProjCost)) { document.getElementById("CurrentProjectCost").style.color = "green" } else { document.getElementById("CurrentProjectCost").style.color = "red" } document.getElementById("CurrentProjectActualCost").innerHTML = "$" + myProjActualCost; if (Number(myProjActualCost) <= Number(avgProjActualCost)) { document.getElementById("CurrentProjectActualCost").style.color = "green" } else { document.getElementById("CurrentProjectActualCost").style.color = "red" } document.getElementById("CurrentProjectWork").innerHTML = myProjWork + " hrs"; if (Number(myProjWork) <= Number(avgProjWork)) { document.getElementById("CurrentProjectWork").style.color = "red" } else { document.getElementById("CurrentProjectWork").style.color = "green" } document.getElementById("CurrentProjectPercentComplete").innerHTML = myProjPercentCompleted + "%"; if (Number(myProjPercentCompleted) <= Number(avgProjPercentCompleted)) { document.getElementById("CurrentProjectPercentComplete").style.color = "red" } else { document.getElementById("CurrentProjectPercentComplete").style.color = "green" } } else { document.getElementById("CurrentProjectCost").innerHTML = "NA"; document.getElementById("CurrentProjectCost").style.color = "blue" document.getElementById("CurrentProjectActualCost").innerHTML = "NA"; document.getElementById("CurrentProjectActualCost").style.color = "blue" document.getElementById("CurrentProjectWork").innerHTML = "NA"; document.getElementById("CurrentProjectWork").style.color = "blue" document.getElementById("CurrentProjectPercentComplete").innerHTML = "NA"; document.getElementById("CurrentProjectPercentComplete").style.color = "blue" } }
Testar o suplemento HelloProjectOData
Para testar e depurar o suplemento HelloProjectOData com o Visual Studio, Project Professional tem de ser instalado no computador de desenvolvimento. Para habilitar cenários de teste diferentes, certifique-se de poder escolher se o Project abre no caso de arquivos no computador local ou se ele se conecta ao Project Web App. Seguem-se passos de exemplo.
No separador Ficheiro , selecione o separador Informações na vista Backstage e, em seguida, selecione Gerir Contas.
Na caixa de diálogo Contas do Project Web App , a lista Contas disponíveis pode ter várias contas do Project Web App, além da conta de Computador local. Na seção Ao iniciar, selecione Escolher uma conta.
Feche o Project para que o Visual Studio possa iniciá-lo na depuração do suplemento.
Os testes básicos devem incluir o seguinte:
Execute o suplemento no Visual Studio, e abra um projeto publicado do Project Web App que contém dados de custos e trabalho. Verifique se o suplemento apresenta o ponto final ProjectData e apresenta corretamente os dados de custo e trabalho na tabela. Você pode usar a saída no controle odataText para verificar a consulta REST e outras informações.
Execute o suplemento novamente, onde seleciona o perfil de computador local na caixa de diálogo Iniciar sessão quando o Project é iniciado. Abra um arquivo .mpp local e teste o suplemento. Verifique se o suplemento exibe uma mensagem de erro ao tentar acessar o ponto de extremidade ProjectData.
Execute o suplemento novamente e crie um projeto com tarefas com dados de custo e de trabalho. Você pode salvar o projeto no Project Web App, mas não o publique. Verifique se o suplemento apresenta dados do Project Server, mas NA para o projeto atual.
Procedimento 6. Testar o suplemento
Execute Project Professional, ligue-se ao Project Web App e, em seguida, crie um projeto de teste. Atribua tarefas aos recursos locais ou a recursos da empresa, defina vários valores de porcentagem concluída em algumas tarefas e publique o projeto. Feche o projeto, o que permite que o Visual Studio inicie o Project para depurar o suplemento.
No Visual Studio, prima F5. Faça logon no Project Web App e abra o projeto que você criou na etapa anterior. Você pode abrir o projeto no modo somente leitura ou no modo de edição.
No separador PROJETO do friso, na lista pendente Suplementos do Office, selecione Hello ProjectData (consulte a Figura 5). O botão Comparar Todos os Projetos deve estar desativado.
Figura 5. Iniciar o suplemento HelloProjectOData
No painel de tarefas Hello ProjectData, selecione Obter ProjectPonto Final de Dados. A linha projectDataEndPoint deve mostrar o URL do serviço ProjectData e o botão Comparar Todos os Projetos deve estar ativado (consulte a Figura 6).
Selecione Comparar Todos os Projetos. O suplemento pode ser colocado em pausa enquanto obtém dados do serviço ProjectData e, em seguida, deve apresentar a média formatada e os valores atuais na tabela.
Figura 6. Ver resultados da consulta REST
Examine a saída na caixa de texto. Deve mostrar o caminho do documento, a consulta REST, status informações e os resultados JSON das chamadas para
ajax
eparseODataResult
. A saída ajuda a compreender, criar e depurar código naparseODataResult
função, comoprojCost += Number(res.d.results[i].ProjectCost);
.Segue-se um exemplo da saída com quebras de linha e espaços adicionados ao texto para maior clareza, para três projetos numa instância do Project Web App.
Document path: <>\WinProj test1 REST query: http://sphvm-37189/pwa/_api/ProjectData/Projects?$filter=ProjectName ne 'Timesheet Administrative Work Items' &$select=ProjectId, ProjectName, ProjectCost, ProjectWork, ProjectPercentCompleted, ProjectActualCost textStatus: success ContentType: application/json;odata=verbose;charset=utf-8 Status: 200 ResponseText: {"d":{"results":[ {"__metadata": {"id":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'ce3d0d65-3904-e211-96cd-00155d157123')", "uri":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'ce3d0d65-3904-e211-96cd-00155d157123')", "type":"ReportingData.Project"}, "ProjectId":"ce3d0d65-3904-e211-96cd-00155d157123", "ProjectActualCost":"0.000000", "ProjectCost":"0.000000", "ProjectName":"Task list created in PWA", "ProjectPercentCompleted":0, "ProjectWork":"16.000000"}, {"__metadata": {"id":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'c31023fc-1404-e211-86b2-3c075433b7bd')", "uri":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'c31023fc-1404-e211-86b2-3c075433b7bd')", "type":"ReportingData.Project"}, "ProjectId":"c31023fc-1404-e211-86b2-3c075433b7bd", "ProjectActualCost":"700.000000", "ProjectCost":"2400.000000", "ProjectName":"WinProj test 2", "ProjectPercentCompleted":29, "ProjectWork":"48.000000"}, {"__metadata": {"id":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'dc81fbb2-b801-e211-9d2a-3c075433b7bd')", "uri":"http://sphvm-37189/pwa/_api/ProjectData/Projects(guid'dc81fbb2-b801-e211-9d2a-3c075433b7bd')", "type":"ReportingData.Project"}, "ProjectId":"dc81fbb2-b801-e211-9d2a-3c075433b7bd", "ProjectActualCost":"1900.000000", "ProjectCost":"5200.000000", "ProjectName":"WinProj test1", "ProjectPercentCompleted":37, "ProjectWork":"104.000000"} ]}}
Pare a depuração (prima Shift + F5) e, em seguida, prima F5 novamente para executar uma nova instância do Project. Na caixa de diálogo Login, escolha o perfil local Computador, e não o Project Web App. Crie ou abra um arquivo .mpp de projeto local, abra o painel de tarefas Hello ProjectData e selecione Obter Ponto de Extremidade de ProjectData. O suplemento deve mostrar um erro Sem ligação! (veja a Figura 7) e o botão Comparar Todos os Projetos deve permanecer desativado.
Figura 7. Utilizar o suplemento sem uma ligação do Project Web App
Pare a depuração e, em seguida, prima F5 novamente. Faça logon no Project Web App e crie um projeto com dados de custo e de trabalho. Você pode salvar o projeto, mas não o publique.
No painel de tarefas Hello ProjectData, quando seleciona Comparar Todos os Projetos, deverá ver um NA azul para campos na coluna Atual (consulte a Figura 8).
Figura 8. Comparar um projeto não publicado com outros projetos
Mesmo que seu suplemento tenha funcionado corretamente nos testes anteriores, há outros testes que devem ser executados. Por exemplo:
Abra um projeto do Project Web App que não tenha nenhum dado de custo ou de trabalho para as tarefas. Deverá ver valores de zero nos campos na coluna Atual .
Teste um projeto sem tarefas.
Se você modificar o suplemento e publicá-lo, deve executar testes semelhantes novamente com o suplemento publicado. Para outras considerações, confira Próximas etapas.
Observação
Existem limites à quantidade de dados que podem ser devolvidos numa consulta do serviço ProjectData ; a quantidade de dados varia de acordo com a entidade. Por exemplo, o Projects
conjunto de entidades tem um limite predefinido de 100 projetos por consulta, mas o Risks
conjunto de entidades tem um limite predefinido de 200. For a production installation, the code in the HelloProjectOData example should be modified to enable queries of more than 100 projects. For more information, see Next steps and Querying OData feeds for Project reporting data.
Exemplo de código para o suplemento de HelloProjectOData
Arquivo HelloProjectOData.html
O código a seguir está no arquivo Pages\HelloProjectOData.html
do projeto HelloProjectODataWeb.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<title>Test ProjectData Service</title>
<link rel="stylesheet" type="text/css" href="../Content/Office.css" />
<!-- Add your CSS styles to the following file. -->
<link rel="stylesheet" type="text/css" href="../Content/App.css" />
<!-- Use the CDN reference to the mini-version of jQuery when deploying your add-in. -->
<!--<script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.0.min.js"></script> -->
<script src="../Scripts/jquery-1.7.1.js"></script>
<!-- Use the CDN reference to Office.js when deploying your add-in -->
<!--<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>-->
<!-- Use the local script references for Office.js to enable offline debugging -->
<script src="../Scripts/Office/1.0/MicrosoftAjax.js"></script>
<script src="../Scripts/Office/1.0/Office.js"></script>
<!-- Add your JavaScript to the following files. -->
<script src="../Scripts/HelloProjectOData.js"></script>
<script src="../Scripts/SurfaceErrors.js"></script>
</head>
<body>
<div id="SectionContent">
<div id="odataQueries">
ODATA REST QUERY
</div>
<div id="odataInfo">
<button class="button-wide" onclick="setOdataUrl()">Get ProjectData Endpoint</button>
<br />
<br />
<span class="rest" id="projectDataEndPoint">Endpoint of the
<strong>ProjectData</strong> service</span>
<br />
</div>
<div id="compareProjectData">
<button class="button-wide" disabled="disabled" id="compareProjects"
onclick="retrieveOData()">
Compare All Projects</button>
<br />
</div>
</div>
<div id="corpInfo">
<table class="infoTable" aria-readonly="True" style="width: 100%;">
<tr>
<td class="heading_leftCol"></td>
<td class="heading_midCol"><strong>Average</strong></td>
<td class="heading_rightCol"><strong>Current</strong></td>
</tr>
<tr>
<td class="row_leftCol"><strong>Project Cost</strong></td>
<td class="row_midCol" id="AverageProjectCost">&nbsp;</td>
<td class="row_rightCol" id="CurrentProjectCost">&nbsp;</td>
</tr>
<tr>
<td class="row_leftCol"><strong>Project Actual Cost</strong></td>
<td class="row_midCol" id="AverageProjectActualCost">&nbsp;</td>
<td class="row_rightCol" id="CurrentProjectActualCost">&nbsp;</td>
</tr>
<tr>
<td class="row_leftCol"><strong>Project Work</strong></td>
<td class="row_midCol" id="AverageProjectWork">&nbsp;</td>
<td class="row_rightCol" id="CurrentProjectWork">&nbsp;</td>
</tr>
<tr>
<td class="row_leftCol"><strong>Project % Complete</strong></td>
<td class="row_midCol" id="AverageProjectPercentComplete">&nbsp;</td>
<td class="row_rightCol" id="CurrentProjectPercentComplete">&nbsp;</td>
</tr>
</table>
</div>
<img alt="Corporation" class="logo" src="../../images/NewLogo.png" />
<br />
<textarea id="odataText" rows="12" cols="40"></textarea>
</body>
</html>
Arquivo HelloProjectOData.js
O código a seguir está no arquivo Scripts\Office\HelloProjectOData.js
do projeto HelloProjectODataWeb.
/* File: HelloProjectOData.js
* JavaScript functions for the HelloProjectOData example task pane app.
* October 2, 2012
*/
let PROJDATA = "/_api/ProjectData";
let PROJQUERY = "/Projects?";
let QUERY_FILTER = "$filter=ProjectName ne 'Timesheet Administrative Work Items'";
let QUERY_SELECT1 = "&$select=ProjectId, ProjectName";
let QUERY_SELECT2 = ", ProjectCost, ProjectWork, ProjectPercentCompleted, ProjectActualCost";
let _pwa; // URL of Project Web App.
let _projectUid; // GUID of the active project.
let _docUrl; // Path of the project document.
let _odataUrl = ""; // URL of the OData service: http[s]://ServerName /ProjectServerName /_api/ProjectData
// The initialize function is required for all add-ins.
Office.initialize = function (reason) {
// Checks for the DOM to load using the jQuery ready method.
$(document).ready(function () {
// After the DOM is loaded, app-specific code can run.
});
}
// Set the global variables, enable the Compare All Projects button,
// and display the URL of the ProjectData service.
// Display an error if Project is not connected with Project Web App.
function setOdataUrl() {
Office.context.document.getProjectFieldAsync(
Office.ProjectProjectFields.ProjectServerUrl,
function (asyncResult) {
if (asyncResult.status == Office.AsyncResultStatus.Succeeded) {
_pwa = String(asyncResult.value.fieldValue);
// If you debug with Visual Studio on a local Project Server computer,
// uncomment the following lines to use the localhost URL.
//let localhost = location.host.split(":", 1);
//let pwaStartPosition = _pwa.lastIndexOf("/");
//let pwaLength = _pwa.length - pwaStartPosition;
//let pwaName = _pwa.substr(pwaStartPosition, pwaLength);
//_pwa = location.protocol + "//" + localhost + pwaName;
if (_pwa.substring(0, 4) == "http") {
_odataUrl = _pwa + PROJDATA;
$("#compareProjects").removeAttr("disabled");
getProjectGuid();
}
else {
_odataUrl = "No connection!";
throwError(_odataUrl, "You are not connected to Project Web App.");
}
getDocumentUrl();
$("#projectDataEndPoint").text(_odataUrl);
}
else {
throwError(asyncResult.error.name, asyncResult.error.message);
}
}
);
}
// Get the GUID of the active project.
function getProjectGuid() {
Office.context.document.getProjectFieldAsync(
Office.ProjectProjectFields.GUID,
function (asyncResult) {
if (asyncResult.status == Office.AsyncResultStatus.Succeeded) {
_projectUid = asyncResult.value.fieldValue;
}
else {
throwError(asyncResult.error.name, asyncResult.error.message);
}
}
);
}
// Get the path of the project in Project web app, which is in the form <>\ProjectName .
function getDocumentUrl() {
_docUrl = "Document path:\r\n" + Office.context.document.url;
}
// Functions to get and parse the Project Server reporting data./
// Get data about all projects on Project Server,
// by using a REST query with the ajax method in jQuery.
function retrieveOData() {
let restUrl = _odataUrl + PROJQUERY + QUERY_FILTER + QUERY_SELECT1 + QUERY_SELECT2;
let accept = "application/json; odata=verbose";
accept.toLocaleLowerCase();
// Enable cross-origin scripting (required by jQuery 1.5 and later).
// This does not work with Project on the web.
$.support.cors = true;
$.ajax({
url: restUrl,
type: "GET",
contentType: "application/json",
data: "", // Empty string for the optional data.
//headers: { "Accept": accept },
beforeSend: function (xhr) {
xhr.setRequestHeader("ACCEPT", accept);
},
complete: function (xhr, textStatus) {
// Create a message to display in the text box.
let message = "\r\ntextStatus: " + textStatus +
"\r\nContentType: " + xhr.getResponseHeader("Content-Type") +
"\r\nStatus: " + xhr.status +
"\r\nResponseText:\r\n" + xhr.responseText;
// xhr.responseText is the result from an XmlHttpRequest, which
// contains the JSON response from the OData service.
parseODataResult(xhr.responseText, _projectUid);
// Write the document name, response header, status, and JSON to the odataText control.
$("#odataText").text(_docUrl);
$("#odataText").append("\r\nREST query:\r\n" + restUrl);
$("#odataText").append(message);
if (xhr.status != 200 && xhr.status != 1223 && xhr.status != 201) {
$("#odataInfo").append("<div>" + htmlEncode(restUrl) + "</div>");
}
},
error: getProjectDataErrorHandler
});
}
function getProjectDataErrorHandler(data, errorCode, errorMessage) {
$("#odataText").text("Error code: " + errorCode + "\r\nError message: \r\n"
+ errorMessage);
throwError(errorCode, errorMessage);
}
// Calculate the average values of actual cost, cost, work, and percent complete
// for all projects, and compare with the values for the current project.
function parseODataResult(oDataResult, currentProjectGuid) {
// Deserialize the JSON string into a JavaScript object.
let res = Sys.Serialization.JavaScriptSerializer.deserialize(oDataResult);
let len = res.d.results.length;
let projActualCost = 0;
let projCost = 0;
let projWork = 0;
let projPercentCompleted = 0;
let myProjectIndex = -1;
for (i = 0; i < len; i++) {
// If the current project GUID matches the GUID from the OData query,
// then store the project index.
if (currentProjectGuid.toLocaleLowerCase() == res.d.results[i].ProjectId) {
myProjectIndex = i;
}
projCost += Number(res.d.results[i].ProjectCost);
projWork += Number(res.d.results[i].ProjectWork);
projActualCost += Number(res.d.results[i].ProjectActualCost);
projPercentCompleted += Number(res.d.results[i].ProjectPercentCompleted);
}
let avgProjCost = projCost / len;
let avgProjWork = projWork / len;
let avgProjActualCost = projActualCost / len;
let avgProjPercentCompleted = projPercentCompleted / len;
// Round off cost to two decimal places, and round off other values to one decimal place.
avgProjCost = avgProjCost.toFixed(2);
avgProjWork = avgProjWork.toFixed(1);
avgProjActualCost = avgProjActualCost.toFixed(2);
avgProjPercentCompleted = avgProjPercentCompleted.toFixed(1);
// Display averages in the table, with the correct units.
document.getElementById("AverageProjectCost").innerHTML = "$"
+ avgProjCost;
document.getElementById("AverageProjectActualCost").innerHTML
= "$" + avgProjActualCost;
document.getElementById("AverageProjectWork").innerHTML
= avgProjWork + " hrs";
document.getElementById("AverageProjectPercentComplete").innerHTML
= avgProjPercentCompleted + "%";
// Calculate and display values for the current project.
if (myProjectIndex != -1) {
let myProjCost = Number(res.d.results[myProjectIndex].ProjectCost);
let myProjWork = Number(res.d.results[myProjectIndex].ProjectWork);
let myProjActualCost = Number(res.d.results[myProjectIndex].ProjectActualCost);
let myProjPercentCompleted = Number(res.d.results[myProjectIndex].ProjectPercentCompleted);
myProjCost = myProjCost.toFixed(2);
myProjWork = myProjWork.toFixed(1);
myProjActualCost = myProjActualCost.toFixed(2);
myProjPercentCompleted = myProjPercentCompleted.toFixed(1);
document.getElementById("CurrentProjectCost").innerHTML = "$" + myProjCost;
if (Number(myProjCost) <= Number(avgProjCost)) {
document.getElementById("CurrentProjectCost").style.color = "green"
}
else {
document.getElementById("CurrentProjectCost").style.color = "red"
}
document.getElementById("CurrentProjectActualCost").innerHTML = "$" + myProjActualCost;
if (Number(myProjActualCost) <= Number(avgProjActualCost)) {
document.getElementById("CurrentProjectActualCost").style.color = "green"
}
else {
document.getElementById("CurrentProjectActualCost").style.color = "red"
}
document.getElementById("CurrentProjectWork").innerHTML = myProjWork + " hrs";
if (Number(myProjWork) <= Number(avgProjWork)) {
document.getElementById("CurrentProjectWork").style.color = "red"
}
else {
document.getElementById("CurrentProjectWork").style.color = "green"
}
document.getElementById("CurrentProjectPercentComplete").innerHTML = myProjPercentCompleted + "%";
if (Number(myProjPercentCompleted) <= Number(avgProjPercentCompleted)) {
document.getElementById("CurrentProjectPercentComplete").style.color = "red"
}
else {
document.getElementById("CurrentProjectPercentComplete").style.color = "green"
}
}
else { // The current project is not published.
document.getElementById("CurrentProjectCost").innerHTML = "NA";
document.getElementById("CurrentProjectCost").style.color = "blue"
document.getElementById("CurrentProjectActualCost").innerHTML = "NA";
document.getElementById("CurrentProjectActualCost").style.color = "blue"
document.getElementById("CurrentProjectWork").innerHTML = "NA";
document.getElementById("CurrentProjectWork").style.color = "blue"
document.getElementById("CurrentProjectPercentComplete").innerHTML = "NA";
document.getElementById("CurrentProjectPercentComplete").style.color = "blue"
}
}
Arquivo App.css
O código a seguir está no arquivo Content\App.css
do projeto HelloProjectODataWeb.
/*
* File: App.css for the HelloProjectOData app.
* Updated: 10/2/2012
*/
body
{
font-size: 11pt;
}
h1
{
font-size: 22pt;
}
h2
{
font-size: 16pt;
}
/******************************************************************
Code label class
******************************************************************/
.rest
{
font-family: 'Courier New';
font-size: 0.9em;
}
/******************************************************************
Button classes
******************************************************************/
.button-wide {
width: 210px;
margin-top: 2px;
}
.button-narrow
{
width: 80px;
margin-top: 2px;
}
/******************************************************************
Table styles
******************************************************************/
.infoTable
{
text-align: center;
vertical-align: middle
}
.heading_leftCol
{
width: 20px;
height: 20px;
}
.heading_midCol
{
width: 100px;
height: 20px;
font-size: medium;
font-weight: bold;
}
.heading_rightCol
{
width: 101px;
height: 20px;
font-size: medium;
font-weight: bold;
}
.row_leftCol
{
width: 20px;
font-size: small;
font-weight: bold;
}
.row_midCol
{
width: 100px;
}
.row_rightCol
{
width: 101px;
}
.logo
{
width: 135px;
height: 53px;
}
Arquivo SurfaceErrors.js
O código seguinte inclui uma throwError
função que cria um Toast
objeto.
/*
* Show error messages in a "toast" notification.
*/
// Throws a custom defined error.
function throwError(errTitle, errMessage) {
try {
// Define and throw a custom error.
let customError = { name: errTitle, message: errMessage }
throw customError;
}
catch (err) {
// Catch the error and display it to the user.
Toast.showToast(err.name, err.message);
}
}
// Add a dynamically-created div "toast" for displaying errors to the user.
let Toast = {
Toast: "divToast",
Close: "btnClose",
Notice: "lblNotice",
Output: "lblOutput",
// Show the toast with the specified information.
showToast: function (title, message) {
if (document.getElementById(this.Toast) == null) {
this.createToast();
}
document.getElementById(this.Notice).innerText = title;
document.getElementById(this.Output).innerText = message;
$("#" + this.Toast).hide();
$("#" + this.Toast).show("slow");
},
// Create the display for the toast.
createToast: function () {
let divToast;
let lblClose;
let btnClose;
let divOutput;
let lblOutput;
let lblNotice;
// Create the container div.
divToast = document.createElement("div");
let toastStyle = "background-color:rgba(220, 220, 128, 0.80);" +
"position:absolute;" +
"bottom:0px;" +
"width:90%;" +
"text-align:center;" +
"font-size:11pt;";
divToast.setAttribute("style", toastStyle);
divToast.setAttribute("id", this.Toast);
// Create the close button.
lblClose = document.createElement("div");
lblClose.setAttribute("id", this.Close);
let btnStyle = "text-align:right;" +
"padding-right:10px;" +
"font-size:10pt;" +
"cursor:default";
lblClose.setAttribute("style", btnStyle);
lblClose.appendChild(document.createTextNode("CLOSE "));
btnClose = document.createElement("span");
btnClose.setAttribute("style", "cursor:pointer;");
btnClose.setAttribute("onclick", "Toast.close()");
btnClose.innerText = "X";
lblClose.appendChild(btnClose);
// Create the div to contain the toast title and message.
divOutput = document.createElement("div");
divOutput.setAttribute("id", "divOutput");
let outputStyle = "margin-top:0px;";
divOutput.setAttribute("style", outputStyle);
lblNotice = document.createElement("span");
lblNotice.setAttribute("id", this.Notice);
let labelStyle = "font-weight:bold;margin-top:0px;";
lblNotice.setAttribute("style", labelStyle);
lblOutput = document.createElement("span");
lblOutput.setAttribute("id", this.Output);
// Add the child nodes to the toast div.
divOutput.appendChild(lblNotice);
divOutput.appendChild(document.createElement("br"));
divOutput.appendChild(lblOutput);
divToast.appendChild(lblClose);
divToast.appendChild(divOutput);
// Add the toast div to the document body.
document.body.appendChild(divToast);
},
// Close the toast.
close: function () {
$("#" + this.Toast).hide("slow");
}
}
Próximas etapas
Se HelloProjectOData fosse um suplemento de produção para ser vendido no AppSource ou distribuído num catálogo de aplicações do SharePoint, seria concebido de forma diferente. Por exemplo, não haveria nenhuma saída de depuração em uma caixa de texto e provavelmente nenhum botão para obter o ponto de extremidade ProjectData. Também teria de reescrever a retrieveOData
função para processar instâncias do Project Web App que tenham mais de 100 projetos.
O suplemento deveria conter mais verificações de erro, além de lógica para capturar e explicar ou mostrar casos extremos. Por exemplo, se uma instância do Project Web App tiver mil projetos com uma duração média de cinco dias e custo médio de US$ 2.400, e o projeto ativo for o único que tem uma duração de mais de 20 dias, a comparação de custo e trabalho poderá ficar desequilibrada. Isso poderia ser exibido com um gráfico de frequência. Você poderia adicionar opções para exibir a duração, comparar projetos de tamanhos semelhantes ou comparar projetos de um mesmo departamento ou de departamentos diferentes. Ou poderia adicionar uma forma de o usuário selecionar os campos a exibir em uma lista.
Para outras consultas do serviço ProjectData , existem limites para o comprimento da cadeia de consulta, o que afeta o número de passos que uma consulta pode efetuar de uma coleção principal para um objeto numa coleção subordinada. Por exemplo, uma consulta de duas etapas de Projects para Tasks para itens de tarefa funciona, mas uma consulta de três etapas, como Projects para Tasks para Assignments para itens de atribuição pode exceder o comprimento máximo de URL padrão. Para obter mais informações, veja Consultar feeds OData para dados de relatórios do Project.
Se modificar o suplemento HelloProjectOData para utilização de produção, siga os seguintes passos.
No arquivo HelloProjectOData.html, para obter melhor desempenho, mude a referência ao office.js do projeto local para a referência da CDN:
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
Reescreva a
retrieveOData
função para ativar consultas de mais de 100 projetos. Por exemplo, você pode obter o número de projetos com uma consulta~/ProjectData/Projects()/$count
e usar os operadores $skip e $top na consulta REST para dados de projeto. Execute várias consultas em sequência e tire a média dos dados de cada consulta. Cada consulta para dados do projeto seria do formulário:~/ProjectData/Projects()?skip= [numSkipped]&$top=100&$filter=[filter]&$select=[field1,field2, ???????]
Para obter mais informações, veja Opções de consulta do sistema OData com o ponto final REST. You can also use the Set-SPProjectOdataConfiguration command in Windows PowerShell to override the default page size for a query of the Projects entity set (or any of the 33 entity sets). See ProjectData - Project OData service reference.
Para implantar o suplemento, confira Publicar seu suplemento do Office.