Crear un complemento de Project que use REST con un servicio OData local de Project Server
En este artículo se describe cómo crear un complemento de panel de tareas para Project Profesional que compara los datos de costo y trabajo del proyecto activo con los promedios de todos los proyectos de la instancia actual de Project Web App. El complemento usa REST con la biblioteca jQuery para acceder al servicio de informes OData de ProjectData en Project Server.
El código en este artículo se basa en una muestra desarrollada por Saurabh Sanghvi y Arvind Iyer, Microsoft Corporation.
Requisitos previos
Los siguientes son los requisitos previos para crear un complemento de panel de tareas de Project que lee el servicio ProjectData de una instancia de Project Web App en una instalación local de Project Server.
Project Profesional es necesario para conectarse con Project Web App. El equipo de desarrollo debe tener instalado Project Profesional para habilitar la depuración de F5 con Visual Studio.
Nota:
Project Standard también puede hospedar complementos de panel de tareas, pero no puede iniciar sesión en Project Web App.
Visual Studio 2015 con Office Developer Tools para Visual Studio incluye plantillas para crear complementos de Office y SharePoint. Asegúrese de que ha instalado la versión más reciente de Office Developer Tools; vea la sección Herramientas de las descargas de Complementos de Office y SharePoint.
Los procedimientos y ejemplos de código de este artículo acceden al servicio ProjectData de Project Server en un dominio local. Los métodos jQuery de este artículo no funcionan con Project en la web.
Compruebe que el servicio ProjectData es accesible desde el equipo de desarrollo.
Procedimiento 1. Compruebe que el servicio ProjectData es accesible.
Para permitir que el explorador muestre directamente los datos XML desde una consulta de REST, deshabilite la vista de lectura de fuentes. Para obtener información sobre cómo hacerlo en Internet Explorer, vea El procedimiento 1, paso 4 en Consultar fuentes de OData para los datos de informes de Project.
Consulte el servicio ProjectData mediante el explorador con la siguiente dirección URL:
http://ServerName /ProjectServerName /_api/ProjectData
. Por ejemplo, si la instancia de Project Web App eshttp://MyServer/pwa
, el explorador muestra los resultados siguientes.<?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>
You may have to provide your network credentials to see the results. If the browser shows "Error 403, Access Denied," either you do not have logon permission for that Project Web App instance, or there is a network problem that requires administrative help.
Uso de Visual Studio para crear un complemento de panel de tareas para Project
Office Developer Tools para Visual Studio incluye una plantilla para complementos de panel de tareas para Project. Si crea una solución denominada HelloProjectOData, la solución contiene los dos proyectos de Visual Studio siguientes:
El proyecto del complemento adopta el nombre de la solución. Incluye el archivo de manifiesto de solo complemento para el complemento y tiene como destino .NET Framework 4.5. El procedimiento 3 muestra los pasos para modificar el manifiesto del complemento HelloProjectOData .
El proyecto web se denomina HelloProjectODataWeb. Incluye páginas web, archivos JavaScript, archivos CSS, imágenes, referencias y archivos de configuración para el contenido web en el panel de tareas. El proyecto web se orienta a .NET Framework 4. En los procedimientos 4 y 5 se muestra cómo modificar los archivos en el proyecto web para crear la funcionalidad del complemento HelloProjectOData.
Procedimiento 2. Creación del complemento HelloProjectOData para Project
Ejecute Visual Studio 2015 como administrador y, a continuación, seleccione Nuevo proyecto en la página Inicio.
En el cuadro de diálogo Nuevo proyecto , expanda los nodos Plantillas, Visual C# y Office/SharePoint y, a continuación, seleccione Complementos de Office. Seleccione .NET Framework 4.5.2 en la lista desplegable del marco de destino en la parte superior del panel central y, a continuación, seleccione Complemento de Office (vea la siguiente captura de pantalla).
Para ubicar ambos proyectos de Visual Studio en el mismo directorio, seleccione Crear directorio para la solución y después vaya hasta la ubicación deseada.
En el campo Nombre , escribaHelloProjectOData y, a continuación, elija Aceptar.
Figura 1. Crear un complemento para Office
En el cuadro de diálogo Elegir el tipo de complemento, seleccione Panel de tareas y elija Siguiente (consulte la siguiente captura de pantalla).
Figura 2. Elección del tipo de complemento para crear
En el cuadro de diálogo Elija las aplicaciones host, desactive todas las casillas excepto la casilla Project (vea la siguiente captura de pantalla) y elija Finalizar.
Figura 3. Elección de la aplicación host
Visual Studio crea el proyecto HelloProjectOdata y el proyecto HelloProjectODataWeb .
La carpeta Complementos (vea la siguiente captura de pantalla) contiene el archivo App.css para estilos CSS personalizados. En la subcarpeta Inicio, el archivo Home.html contiene referencias a los archivos CSS y JavaScript que el complemento usa y el contenido de HTML5 para el complemento. Además, el archivo Home.js es para el código JavaScript personalizado. La carpeta Scripts incluye los archivos de biblioteca jQuery. La subcarpeta Office incluye las bibliotecas de JavaScript como office.js y project-15.js, además de las bibliotecas de idioma para cadenas estándares de los complementos de Office. En la carpeta Content, el archivo Office.css contiene los estilos predeterminados para todos los complementos de Office.
Figura 4. Ver los archivos de proyecto web predeterminados en Explorador de soluciones
El manifiesto del proyecto HelloProjectOData es el archivo HelloProjectOData.xml. Si lo desea, puede modificar el manifiesto para agregar una descripción del complemento, una referencia a un icono, información para idiomas adicionales y otras configuraciones. El procedimiento 3 simplemente modifica el nombre y la descripción del complemento, y agrega un icono.
Para obtener más información sobre el manifiesto, vea Manifiesto de complementos de Office y Referencia de esquema para manifiestos de complementos de Office.
Procedimiento 3. Modificación del manifiesto del complemento
En Visual Studio, abra el archivo HelloProjectOData.xml.
El nombre para mostrar predeterminado es el nombre del proyecto de Visual Studio ("HelloProjectOData"). Por ejemplo, cambie el valor predeterminado del <elemento DisplayName> a "Hello ProjectData".
La descripción predeterminada es también "HelloProjectOData". Por ejemplo, cambie el valor predeterminado del elemento Descripción a "Probar consultas de REST del servicio ProjectData".
Agregue un icono para mostrar en la lista desplegable Complementos para Office de la pestaña PROYECTO de la cinta. Puede agregar un archivo de icono en la solución de Visual Studio o usar una dirección URL de un icono.
En los pasos siguientes se muestra cómo agregar un archivo de icono a la solución de Visual Studio.
En Explorador de soluciones, vaya a la carpeta denominada Imágenes.
Para que aparezca en la lista desplegable Complementos para Office, el icono debe tener 32 x 32 píxeles. Use su propio icono de 32 x 32; o bien, copie la siguiente imagen en un archivo denominado NewIcon.pngy, a continuación, agregue ese archivo a la
HelloProjectODataWeb\Images
carpeta .En el HelloProjectOData.xml, agregue un <elemento IconUrl> debajo del <elemento Description> , donde el valor de la dirección URL del icono es la ruta de acceso relativa al archivo de icono 32x32. Por ejemplo, agregue la línea siguiente:
<IconUrl DefaultValue="~remoteAppUrl/Images/NewIcon.png" />
. El archivo HelloProjectOData.xml ahora contiene lo siguiente (el 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>
Creación del contenido HTML para el complemento HelloProjectOData
El complemento HelloProjectOData es un ejemplo que incluye la depuración y la salida de errores; no está pensado para su uso en producción. Antes de empezar a codificar el contenido HTML, diseñe la interfaz de usuario y la experiencia del usuario para el complemento y describa las funciones de JavaScript que interactúan con el código HTML. Para obtener más información, vea Directrices de diseño para complementos de Office.
El panel de tareas muestra el nombre para mostrar del complemento en la parte superior, que es el valor del <elemento DisplayName> del manifiesto. El elemento body del archivo HelloProjectOData.html contiene los otros elementos de la interfaz de usuario:
Un subtítulo que indica la función general o tipo de operación, por ejemplo, CONSULTA DE REST ODATA.
El botón Obtener punto de conexión de ProjectData llama a la
setOdataUrl
función para obtener el punto de conexión del servicio ProjectData y mostrarlo en un cuadro de texto. Si Project no está conectado con Project Web App, el complemento llama a un controlador de errores para mostrar un mensaje de error emergente.El botón Comparar todos los proyectos está deshabilitado hasta que el complemento obtiene un extremo de OData válido. Al seleccionar el botón, llama a la
retrieveOData
función , que usa una consulta REST para obtener datos de costo y trabajo del proyecto del servicio ProjectData .Una tabla muestra los valores promedio del costo del proyecto, el costo real y el porcentaje completado. La tabla también compara los valores del proyecto activo actual con el promedio. Si el valor actual es mayor que el promedio para todos los proyectos, el valor aparece en rojo. Si el valor actual es menor que el promedio, el valor aparece en verde. Si el valor actual no está disponible, la tabla muestra un NA azul.
La
retrieveOData
función llama a laparseODataResult
función , que calcula y muestra los valores de la tabla.Nota:
En este ejemplo, los datos de coste y de trabajo para el proyecto activo derivan de los valores publicados. Si cambia los valores en Project, el servicio ProjectData no tendrá los cambios hasta que se publique el proyecto.
Procedimiento 4. Creación del contenido HTML
En el elemento head del archivo Home.html, agregue los elementos de vínculo adicionales para los archivos CSS que use el complemento. La plantilla de proyecto de Visual Studio incluye un vínculo para el archivo App.css que puede usar para los estilos CSS personalizados.
Agregue cualquier elemento de script adicional para las bibliotecas de JavaScript que use el complemento. La plantilla de proyecto incluye vínculos para los archivos jQuery- [versión].js, office.js y MicrosoftAjax.js en la carpeta Scripts .
Nota:
Antes de implementar el complemento, cambie las referencias a office.js y a jQuery por la referencia a la red de entrega de contenido (CDN). La referencia a CDN proporciona la versión más reciente y el mejor rendimiento.
El complemento HelloProjectOData también usa un archivo SurfaceErrors.js , que muestra errores en un mensaje emergente. Copie el código de la sección archivoSurfaceErrors.js de este artículo en la carpeta Scripts\Office del proyecto HelloProjectODataWeb como un nuevo archivo denominado SurfaceErrors.js.
A continuación se muestra el código HTML actualizado para el elemento head , con la línea adicional para el archivo 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>
En el elemento body , elimine el código existente de la plantilla y agregue el código para la interfaz de usuario. Si un elemento se va a completar con datos o a manipular mediante una instrucción jQuery, el elemento debe incluir un único atributo de id exclusivo. En el código siguiente, los atributos id para los elementos button, span y td (definición de celda de tabla) que usan las funciones de jQuery se muestran en negrita.
El código HTML siguiente agrega una imagen gráfica (NewLogo.png), que podría ser un logotipo de 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>
Creación del código JavaScript para el complemento
La plantilla de un complemento de panel de tareas de Project incluye código de inicialización predeterminado diseñado para mostrar acciones básicas de obtención y establecimiento de datos en un documento para un complemento de Office que usa las API comunes. Dado que Project no admite acciones que escriben en el proyecto activo y el complemento HelloProjectOData no usa el getSelectedDataAsync
método , puede eliminar el script dentro de la Office.initialize
función y eliminar la función y getData
la setData
función en el archivo de HelloProjectOData.js predeterminado.
JavaScript incluye constantes globales para la consulta de REST y variables globales que se usan en varias funciones. El botón Obtener punto de conexión de ProjectData llama a la setOdataUrl
función , que inicializa las variables globales y determina si Project está conectado con Project Web App.
El resto del archivo HelloProjectOData.js incluye dos funciones: se llama a la retrieveOData
función cuando el usuario selecciona Comparar todos los proyectos; y la parseODataResult
función calcula los promedios y, a continuación, rellena la tabla de comparación con valores con formato para el color y las unidades.
Procedimiento 5. Creación del código JavaScript
Elimine todo el código del archivo de HelloProjectOData.js predeterminado y agregue las variables globales y
Office.initialize
la función. Los nombres de variables que son todas mayúsculas implican que son constantes; más adelante se usan con la_pwa
variable para crear la consulta REST en este ejemplo.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. }); });
Agregar
setOdataUrl
y funciones relacionadas. LasetOdataUrl
función llama agetProjectGuid
egetDocumentUrl
inicializa las variables globales. En el método getProjectFieldAsync, la función anónima para el parámetro callback habilita el botón Comparar todos los proyectos mediante elremoveAttr
método de la biblioteca jQuery y, a continuación, muestra la dirección URL del servicio ProjectData . Si Project no está conectado con Project Web App, la función produce un error, que muestra un mensaje de error emergente. El archivo SurfaceErrors.js incluye lathrowError
función .Nota:
Si ejecuta Visual Studio en el equipo de Project Server, para usar la depuración F5 , quite la marca de comentario del código después de la línea que inicializa la
_pwa
variable global. Para habilitar el uso del método jQueryajax
al depurar en el equipo de Project Server, debe establecer ellocalhost
valor de la dirección URL de PWA. Si ejecuta Visual Studio en un equipo remoto, no se requiere lalocalhost
dirección URL. 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; }
Agregue la
retrieveOData
función , que concatena los valores de la consulta REST y, a continuación, llama a laajax
función en jQuery para obtener los datos solicitados del servicio ProjectData . Lasupport.cors
variable habilita el uso compartido de recursos entre orígenes (CORS) con laajax
función . Si falta lasupport.cors
instrucción o se establece enfalse
, laajax
función devuelve un error Sin transporte .Nota:
El código siguiente funciona con una instalación local de Project Server. Para Project en la web, puede usar OAuth para la autenticación basada en tokens. Para obtener más información, consulte Abordar las limitaciones de la directiva del mismo origen en complementos para Office.
En la
ajax
llamada, puede usar el parámetro headers o el parámetro beforeSend . El parámetro complete es una función anónima para que esté en el mismo ámbito que las variables deretrieveOData
. La función del parámetro complete muestra los resultados en elodataText
control y también llama alparseODataResult
método para analizar y mostrar la respuesta JSON. El parámetro error especifica la función con nombregetProjectDataErrorHandler
, que escribe un mensaje de error en elodataText
control y también usa lathrowError
función para mostrar un mensaje emergente.// 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); }
Agregue la
parseODataResult
función , que deserializa y procesa la respuesta JSON del servicio OData. LaparseODataResult
función calcula los valores medios de los datos de costo y trabajo con una precisión de una o dos posiciones decimales, da formato a los valores con el color correcto y agrega una unidad ( $, hrs o %), y, a continuación, muestra los valores en las celdas de tabla especificadas.Si el GUID del proyecto activo coincide con el
ProjectId
valor, lamyProjectIndex
variable se establece en el índice del proyecto. SimyProjectIndex
indica que el proyecto activo se publica en Project Server, elparseODataResult
método da formato y muestra los datos de costo y trabajo de ese proyecto. Si el proyecto activo no está publicado, los valores del proyecto activo se muestran como na azul.// 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" } }
Prueba del complemento HelloProjectOData
Para probar y depurar el complemento HelloProjectOData con Visual Studio, Project Profesional debe instalarse en el equipo de desarrollo. Para habilitar diferentes escenarios de prueba, asegúrese de elegir si Project se abrirá para archivos en el equipo local o se conectará con Project Web App. A continuación se muestran los pasos de ejemplo.
En la pestaña Archivo , elija la pestaña Información en la vista Backstage y, a continuación, elija Administrar cuentas.
En el cuadro de diálogo Cuentas de project web app , la lista Cuentas disponibles puede tener varias cuentas de Project Web App además de la cuenta de equipo local. En la sección Al iniciar, seleccione Elegir una cuenta.
Cierre Project para que Visual Studio pueda iniciarlo para depurar el complemento.
Las pruebas básicas deben incluir lo siguiente:
Ejecute el complemento desde Visual Studio y después abra un proyecto publicado desde Project Web App que contenga datos de costo y trabajo. Compruebe que el complemento muestra el punto de conexión ProjectData y muestra correctamente los datos de costo y trabajo en la tabla. Puede usar el resultado del control odataText para comprobar la consulta de REST y demás información.
Ejecute el complemento nuevamente y elija el perfil de equipo local en el cuadro de diálogo Inicio de sesión cuando se inicia Project. Abra un archivo .mpp local y después pruebe el complemento. Compruebe que el complemento muestre un mensaje de error cuando intenta obtener el extremo de ProjectData.
Ejecute el complemento nuevamente y cree un proyecto que tenga tareas con datos de costo y trabajo. Puede guardar el proyecto en Project Web App, pero no lo publique. Compruebe que el complemento muestre datos de Project Server, pero que aparezca ND para el proyecto actual.
Procedimiento 6. Probar el complemento
Ejecute Project Profesional, conéctese a Project Web App y, a continuación, cree un proyecto de prueba. Asigne tareas a recursos locales o empresariales, establezca diversos valores de porcentaje completado en algunas tareas y después publique el proyecto. Salga de Project, lo que permitirá que Visual Studio inicie Project para depurar el complemento.
En Visual Studio, presione F5. Inicie sesión en Project Web App y después abra el proyecto que creó en el paso anterior. Puede abrir el proyecto en modo de solo lectura o en modo de edición.
En la pestaña PROYECTO de la cinta de opciones, en la lista desplegable Complementos de Office, seleccione Hello ProjectData (vea la figura 5). El botón Comparar todos los proyectos debe estar deshabilitado.
Figura 5. Inicio del complemento HelloProjectOData
En el panel de tareas Hola ProjectData, seleccione Obtener extremo de ProjectData. La línea projectDataEndPoint debe mostrar la dirección URL del servicio ProjectData y el botón Comparar todos los proyectos debe estar habilitado (vea la figura 6).
Seleccione Comparar todos los proyectos. El complemento puede hacer una pausa mientras recupera datos del servicio ProjectData y después debe mostrar los valores actuales y promedio con formato en la tabla.
Figura 6. Visualización de los resultados de la consulta REST
Revise el resultado en el cuadro de texto. Debe mostrar la ruta de acceso del documento, la consulta REST, la información de estado y los resultados JSON de las llamadas a
ajax
yparseODataResult
. La salida ayuda a comprender, crear y depurar código en laparseODataResult
función, comoprojCost += Number(res.d.results[i].ProjectCost);
.A continuación se muestra un ejemplo de la salida con saltos de línea y espacios agregados al texto para mayor claridad, para tres proyectos en una instancia de 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"} ]}}
Detenga la depuración (presione Mayús+F5) y presione F5 de nuevo para ejecutar una nueva instancia de Project. En el cuadro de diálogo Inicio de sesión , elija el perfil de equipo local, no Project Web App. Cree o abra un archivo .mpp de proyecto local, abra el panel de tareas Hola ProjectData y seleccione Obtener extremo de ProjectData. El complemento debe mostrar un error No connection! (vea la figura 7) y el botón Comparar todos los proyectos debe permanecer deshabilitado.
Figura 7. Uso del complemento sin una conexión de project web app
Detenga la depuración y presione F5 nuevamente. Inicie sesión en Project Web App y cree un proyecto que contenga datos de costo y trabajo. Puede guardar el proyecto, pero no lo publique.
En el panel de tareas Hello ProjectData, al seleccionar Comparar todos los proyectos, debería ver una NA azul para los campos de la columna Actual (vea la figura 8).
Figura 8. Comparación de un proyecto no publicado con otros proyectos
Aun cuando el complemento haya funcionado correctamente en las pruebas anteriores, hay otras pruebas que se deben ejecutar. Por ejemplo:
Abra un proyecto de Project Web App que no tenga datos de costo y trabajo para las tareas. Los valores de los campos de la columna Actual deben estar en cero.
Pruebe un proyecto que no tenga tareas.
Si modifica el complemento y lo publica, debe ejecutar pruebas similares nuevamente con el complemento publicado. Para otras consideraciones, vea Pasos siguientes.
Nota:
Existen límites para la cantidad de datos que se pueden devolver en una consulta del servicio ProjectData y varían según la entidad. Por ejemplo, el Projects
conjunto de entidades tiene un límite predeterminado de 100 proyectos por consulta, pero el Risks
conjunto de entidades tiene un límite predeterminado de 200. En una instalación de producción, el código del ejemplo HelloProjectOData se tendría que modificar para habilitar consultas de más de 100 proyectos. Si desea más información, consulte Pasos siguientes y Consulta de fuentes OData de datos de informes de Project.
Ejemplo de código para el complemento HelloProjectOData
Archivo HelloProjectOData.html
El siguiente código está en el archivo Pages\HelloProjectOData.html
del proyecto 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>
Archivo HelloProjectOData.js
El siguiente código está en el archivo Scripts\Office\HelloProjectOData.js
del proyecto 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 isn't 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 isn't 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"
}
}
Archivo app.css
El siguiente código está en el archivo Content\App.css
del proyecto 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;
}
Archivo surfaceErrors.js
El código siguiente incluye una throwError
función que crea un 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");
}
}
Pasos siguientes
Si HelloProjectOData fuera un complemento de producción que se vendería en AppSource o se distribuiría en un catálogo de aplicaciones de SharePoint, se diseñaría de forma diferente. Por ejemplo, no aparecería el resultado de la depuración en un cuadro de texto y, probablemente, no habría un botón para obtener el extremo de ProjectData. También tendría que volver a escribir la retrieveOData
función para controlar las instancias de Project Web App que tienen más de 100 proyectos.
El complemento debe contener comprobaciones de errores adicionales, además de una lógica para captar y explicar o mostrar casos extremos. Por ejemplo, si una sesión de Project Web App tiene 1000 proyectos con una duración promedio de cinco días y un costo promedio de $2400, y el proyecto activo es el único que tiene una duración de más de 20 días, la comparación entre el costo y el trabajo sería sesgada. Esto se puede mostrar con un gráfico de frecuencias. Se podrían agregar opciones para mostrar la duración, comparar proyectos de duración similar o comparar proyectos del mismo departamento o de departamentos diferentes. O también podría agregar una forma de que el usuario elija de una lista los campos para mostrar.
Para otras consultas del servicio ProjectData , hay límites a la longitud de la cadena de consulta, lo que afecta al número de pasos que una consulta puede realizar de una colección primaria a un objeto de una colección secundaria. Por ejemplo, una consulta de dos pasos de Proyectos a tareas para elemento de tarea funciona, pero una consulta de tres pasos, como Proyectos a tareas a asignaciones al elemento de asignación, puede superar la longitud máxima predeterminada de la dirección URL. Para obtener más información, vea Consultar fuentes de OData para los datos de informes de Project.
Si modifica el complemento HelloProjectOData para su uso en producción, siga estos pasos.
En el archivo HelloProjectOData.html, para un mejor rendimiento, cambie la referencia a office.js del proyecto local por la referencia a la red CDN:
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
Vuelva a escribir la
retrieveOData
función para habilitar consultas de más de 100 proyectos. Por ejemplo, puede obtener el número de proyectos con una consulta~/ProjectData/Projects()/$count
y usar los operadores $skip y $top en la consulta de REST para los datos del proyecto. Ejecute varias consultas en un bucle y, después, calcule el promedio de los datos de cada consulta. Cada consulta de datos del proyecto tendría el formato siguiente:~/ProjectData/Projects()?skip= [numSkipped]&$top=100&$filter=[filter]&$select=[field1,field2, ???????]
Para obtener más información, vea Opciones de consulta del sistema de OData mediante el punto de conexión 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 implementar el complemento, vea Publicar el complemento para Office.