Personalización de la experiencia de usuario mediante complementos hospedados por el proveedor de SharePoint
En este artículo se describen ejemplos que muestran los procedimientos recomendados para personalizar los componentes de la experiencia de usuario de SharePoint, incluidos los siguientes escenarios:
Manipulación de páginas (adición y modificación de una página wiki)
Mostrar complementos y datos en cuadros de diálogo modales
Creación de elementos personalizados de la interfaz de usuario
Representación del lado cliente (implementación de archivos JSLink que personalizan la representación de campos en listas de SharePoint)
Manipulación de elementos web y elementos de complemento (aprovisionar y ejecutar de forma remota un elemento de script de complemento en un complemento hospedado por el proveedor)
Agregación y almacenamiento en caché de datos (mediante el almacenamiento local HTML5 y las cookies HTTP para reducir el número de llamadas de servicio a SharePoint)
Nota:
El código de este artículo se proporciona tal cual, sin garantía de ningún tipo, expresa o implícita, incluidas las garantías implícitas de aptitud para un propósito particular, comerciabilidad o ausencia de infracción.
Manipulación de página
El ejemplo Core.ModifyPages incluye dos escenarios de manipulación de páginas:
- Cree una página wiki.
- Modifique el diseño de una página wiki.
En este ejemplo se usa la biblioteca de páginas de sitio predeterminada y los diseños integrados existentes. También puede actualizarlo para usar una biblioteca de páginas wiki personalizada y diseños personalizados. La interfaz de usuario del complemento incluye dos botones que crean ambas páginas wiki y dos vínculos para ver las páginas wiki que cree.
Página de inicio del ejemplo de manipulación de páginas
El código de ejemplo para el primer escenario determina si ya ha creado la página wiki. Si no es así, agrega el archivo a la biblioteca de páginas de sitio y devuelve su dirección URL.
var newpage = pageLibrary.RootFolder.Files.AddTemplateFile(newWikiPageUrl, TemplateFileType.WikiPage);
ctx.Load(newpage);
ctx.ExecuteQuery();
wikiPageUrl = String.Format("sitepages/{0}", wikiPageName);
En ambos escenarios, el ejemplo agrega el CÓDIGO HTML escrito a través del cuadro de texto de la página de inicio mediante el método AddHtmlToWikiPage en una clase auxiliar denominada LabHelper. Este método inserta el HTML desde el formulario dentro del campo WikiField de la página wiki.
public void AddHtmlToWikiPage(ClientContext ctx, Web web, string folder, string html, string page)
{
Microsoft.SharePoint.Client.Folder pagesLib = web.GetFolderByServerRelativeUrl(folder);
ctx.Load(pagesLib.Files);
ctx.ExecuteQuery();
Microsoft.SharePoint.Client.File wikiPage = null;
foreach (Microsoft.SharePoint.Client.File aspxFile in pagesLib.Files)
{
if (aspxFile.Name.Equals(page, StringComparison.InvariantCultureIgnoreCase))
{
wikiPage = aspxFile;
break;
}
}
if (wikiPage == null)
{
return;
}
ctx.Load(wikiPage);
ctx.Load(wikiPage.ListItemAllFields);
ctx.ExecuteQuery();
string wikiField = (string)wikiPage.ListItemAllFields["WikiField"];
Microsoft.SharePoint.Client.ListItem listItem = wikiPage.ListItemAllFields;
listItem["WikiField"] = html;
listItem.Update();
ctx.ExecuteQuery();
}
El código de ejemplo para el segundo escenario crea una nueva instancia de WebPartEntity . A continuación, usa métodos en una clase auxiliar para rellenar el elemento web con XML. El XML muestra una lista de vínculos promocionados, incluida http://www.bing.com la página principal del repositorio de GitHub Office 365 Developer Patterns and Practices.
WebPartEntity wp2 = new WebPartEntity();
wp2.WebPartXml = new LabHelper().WpPromotedLinks(linksID, string.Format("{0}/Lists/{1}",
Request.QueryString["SPHostUrl"], "Links"),
string.Format("{0}/{1}", Request.QueryString["SPHostUrl"],
scenario2PageUrl), "$Resources:core,linksList");
wp2.WebPartIndex = 1;
wp2.WebPartTitle = "Links";
new LabHelper().AddWebPartToWikiPage(ctx, ctx.Web, "SitePages", wp2, scenario2Page, 2, 1, false);
new LabHelper().AddHtmlToWikiPage(ctx, ctx.Web, "SitePages", htmlEntry.Text, scenario2Page, 2, 2);
this.hplPage2.NavigateUrl = string.Format("{0}/{1}", Request.QueryString["SPHostUrl"], scenario2PageUrl);
El código auxiliar muestra los vínculos promocionados con una tabla dentro de un elemento web XsltListViewWebPart .
Segunda página wiki con XsltListViewWebPart y tabla de vínculos promocionados
El objeto WpPromotedLinks de la instancia labHelper contiene XML que define la apariencia del elemento web que se incrustará en la página wiki. A continuación, el método AddWebPartToWikiPage inserta el elemento web recién definido dentro de una nueva div
etiqueta en la página wiki.
XmlDocument xd = new XmlDocument();
xd.PreserveWhitespace = true;
xd.LoadXml(wikiField);
// Sometimes the wikifield content seems to be surrounded by an additional div.
XmlElement layoutsTable = xd.SelectSingleNode("div/div/table") as XmlElement;
if (layoutsTable == null)
{
layoutsTable = xd.SelectSingleNode("div/table") as XmlElement;
}
XmlElement layoutsZoneInner = layoutsTable.SelectSingleNode(string.Format("tbody/tr[{0}]/td[{1}]/div/div", row, col)) as XmlElement;
// - space element
XmlElement space = xd.CreateElement("p");
XmlText text = xd.CreateTextNode(" ");
space.AppendChild(text);
// - wpBoxDiv
XmlElement wpBoxDiv = xd.CreateElement("div");
layoutsZoneInner.AppendChild(wpBoxDiv);
if (addSpace)
{
layoutsZoneInner.AppendChild(space);
}
XmlAttribute attribute = xd.CreateAttribute("class");
wpBoxDiv.Attributes.Append(attribute);
attribute.Value = "ms-rtestate-read ms-rte-wpbox";
attribute = xd.CreateAttribute("contentEditable");
wpBoxDiv.Attributes.Append(attribute);
attribute.Value = "false";
// - div1
XmlElement div1 = xd.CreateElement("div");
wpBoxDiv.AppendChild(div1);
div1.IsEmpty = false;
attribute = xd.CreateAttribute("class");
div1.Attributes.Append(attribute);
attribute.Value = "ms-rtestate-read " + wpdNew.Id.ToString("D");
attribute = xd.CreateAttribute("id");
div1.Attributes.Append(attribute);
attribute.Value = "div_" + wpdNew.Id.ToString("D");
// - div2
XmlElement div2 = xd.CreateElement("div");
wpBoxDiv.AppendChild(div2);
div2.IsEmpty = false;
attribute = xd.CreateAttribute("style");
div2.Attributes.Append(attribute);
attribute.Value = "display:none";
attribute = xd.CreateAttribute("id");
div2.Attributes.Append(attribute);
attribute.Value = "vid_" + wpdNew.Id.ToString("D");
ListItem listItem = webPartPage.ListItemAllFields;
listItem["WikiField"] = xd.OuterXml;
listItem.Update();
ctx.ExecuteQuery();
Mostrar complementos y datos en cuadros de diálogo modales
El ejemplo Core.Dialog muestra dos métodos para insertar vínculos de cuadros de diálogo modales. Estos vínculos muestran una página de complemento hospedada por el proveedor en un sitio host de SharePoint. El complemento usa el modelo de objetos de cliente (CSOM) para crear la acción personalizada y JavaScript para iniciar y mostrar información dentro del cuadro de diálogo. Dado que parte de esta información procede del sitio host, también usa el modelo de objetos de JavaScript (JSOM) para recuperar información del sitio host. Y dado que el complemento se ejecuta en un dominio diferente al del sitio host de SharePoint, también usa la biblioteca entre dominios de SharePoint para realizar las llamadas al sitio host.
Nota:
Para obtener más información sobre el uso de la biblioteca entre dominios en este escenario, vea Acceso a datos de SharePoint desde complementos mediante la biblioteca entre dominios.
La página de inicio es la página que aparece en el cuadro de diálogo. Para controlar las diferencias en la presentación en función del contexto de presentación (cuadro de diálogo frente a página completa), el complemento determina si se muestra en un cuadro de diálogo. Para ello, usa un parámetro de cadena de consulta que se pasa junto con los vínculos que inician los cuadros de diálogo.
private string SetIsDlg(string isDlgValue)
{
var urlParams = HttpUtility.ParseQueryString(Request.QueryString.ToString());
urlParams.Set("IsDlg", isDlgValue);
return string.Format("{0}://{1}:{2}{3}?{4}", Request.Url.Scheme, Request.Url.Host, Request.Url.Port, Request.Url.AbsolutePath, urlParams.ToString());
}
Por ejemplo, podría elegir mostrar determinados elementos de la interfaz de usuario (como botones) o incluso diseños de página diferentes, dependiendo de si el contenido se muestra o no en un cuadro de diálogo.
La interfaz de usuario de la página de inicio presenta dos opciones para crear vínculos al cuadro de diálogo, junto con una lista de todas las listas en la web host. También presenta los botones Aceptar y Cancelar que puede usar en el contexto del cuadro de diálogo para cerrar el cuadro de diálogo o solicitar acciones adicionales en el complemento.
Al elegir el botón Agregar elemento de menú , el complemento crea un objeto CustomActionEntity que inicia el cuadro de diálogo. A continuación, usa el método de extensión OfficeDevPnP Core denominado AddCustomAction para agregar la nueva acción personalizada al menú Configuración del sitio .
StringBuilder modelDialogScript = new StringBuilder(10);
modelDialogScript.Append("javascript:var dlg=SP.UI.ModalDialog.showModalDialog({url: '");
modelDialogScript.Append(String.Format("{0}", SetIsDlg("1")));
modelDialogScript.Append("', dialogReturnValueCallback:function(res, val) {} });");
// Create a custom action.
CustomActionEntity customAction = new CustomActionEntity()
{
Title = "Office AMS Dialog sample",
Description = "Shows how to start an add-in inside a dialog box.",
Location = "Microsoft.SharePoint.StandardMenu",
Group = "SiteActions",
Sequence = 10000,
Url = modelDialogScript.ToString(),
};
// Add the custom action to the site.
cc.Web.AddCustomAction(customAction);
El método AddCustomAction agrega la acción personalizada a la colección UserCustomActions asociada al sitio de SharePoint.
var newAction = existingActions.Add();
newAction.Description = customAction.Description;
newAction.Location = customAction.Location;
if (customAction.Location == JavaScriptExtensions.SCRIPT_LOCATION)
{
newAction.ScriptBlock = customAction.ScriptBlock;
}
else
{
newAction.Sequence = customAction.Sequence;
newAction.Url = customAction.Url;
newAction.Group = customAction.Group;
newAction.Title = customAction.Title;
newAction.ImageUrl = customAction.ImageUrl;
if (customAction.Rights != null)
{
newAction.Rights = customAction.Rights;
}
}
newAction.Update();
web.Context.Load(web, w => w.UserCustomActions);
web.Context.ExecuteQuery();
Al elegir el botón Agregar página con elemento web Editor de scripts , el complemento usa los métodos AddWikiPage y AddWebPartToWikiPage para crear una página wiki dentro de la biblioteca de páginas del sitio y agregar un elemento web editor de scripts configurado a la página.
string scenario1Page = String.Format("scenario1-{0}.aspx", DateTime.Now.Ticks);
string scenario1PageUrl = cc.Web.AddWikiPage("Site Pages", scenario1Page);
cc.Web.AddLayoutToWikiPage("SitePages", WikiPageLayout.OneColumn, scenario1Page);
WebPartEntity scriptEditorWp = new WebPartEntity();
scriptEditorWp.WebPartXml = ScriptEditorWebPart();
scriptEditorWp.WebPartIndex = 1;
scriptEditorWp.WebPartTitle = "Script editor test";
cc.Web.AddWebPartToWikiPage("SitePages", scriptEditorWp, scenario1Page, 1, 1, false);
El método AddWikiPage carga la biblioteca de páginas del sitio. Si la página wiki especificada por el complemento OfficeDevPnP Core aún no existe, crea la página.
public static string AddWikiPage(this Web web, string wikiPageLibraryName, string wikiPageName)
{
string wikiPageUrl = "";
var pageLibrary = web.Lists.GetByTitle(wikiPageLibraryName);
web.Context.Load(pageLibrary.RootFolder, f => f.ServerRelativeUrl);
web.Context.ExecuteQuery();
var pageLibraryUrl = pageLibrary.RootFolder.ServerRelativeUrl;
var newWikiPageUrl = pageLibraryUrl + "/" + wikiPageName;
var currentPageFile = web.GetFileByServerRelativeUrl(newWikiPageUrl);
web.Context.Load(currentPageFile, f => f.Exists);
web.Context.ExecuteQuery();
if (!currentPageFile.Exists)
{
var newpage = pageLibrary.RootFolder.Files.AddTemplateFile(newWikiPageUrl, TemplateFileType.WikiPage);
web.Context.Load(newpage);
web.Context.ExecuteQuery();
wikiPageUrl = UrlUtility.Combine("sitepages", wikiPageName);
}
return wikiPageUrl;
}
El método AddWebPartToWikiPage inserta el elemento web recién definido dentro de una nueva <div>
etiqueta en la página wiki. A continuación, usa JSOM y la biblioteca entre dominios para recuperar la información que rellena la lista de listas de SharePoint en la web host.
function printAllListNamesFromHostWeb() {
var context;
var factory;
var appContextSite;
var collList;
context = new SP.ClientContext(appWebUrl);
factory = new SP.ProxyWebRequestExecutorFactory(appWebUrl);
context.set_webRequestExecutorFactory(factory);
appContextSite = new SP.AppContextSite(context, spHostUrl);
this.web = appContextSite.get_web();
collList = this.web.get_lists();
context.load(collList);
context.executeQueryAsync(
Function.createDelegate(this, successHandler),
Function.createDelegate(this, errorHandler)
);
https://github.com/SharePoint/PnP/tree/dev/Samples/Core.AppScriptPart
Elementos personalizados de la interfaz de usuario
El ejemplo Branding.UIElementPersonalization muestra cómo usar JavaScript incrustado y los valores almacenados en perfiles de usuario y listas de SharePoint para personalizar los elementos de la interfaz de usuario en la web host. También usa el almacenamiento local HTML5 para minimizar las llamadas al sitio host.
La página de inicio del ejemplo le pide que agregue una de las tres cadenas (XX, YY o ZZ) a la sección Acerca de mí de la página de perfil de usuario.
El complemento ya ha implementado tres imágenes y una lista de SharePoint que contiene títulos y direcciones URL para cada imagen, junto con un campo adicional que vincula cada imagen a una de las tres cadenas. Después de elegir el botón Insertar personalización , el complemento inserta el archivo personalize.js en la colección de acciones personalizadas del usuario.
public void AddPersonalizeJsLink(ClientContext ctx, Web web)
{
string scenarioUrl = String.Format("{0}://{1}:{2}/Scripts", this.Request.Url.Scheme,
this.Request.Url.DnsSafeHost, this.Request.Url.Port);
string revision = Guid.NewGuid().ToString().Replace("-", "");
string personalizeJsLink = string.Format("{0}/{1}?rev={2}", scenarioUrl, "personalize.js", revision);
StringBuilder scripts = new StringBuilder(@"
var headID = document.getElementsByTagName('head')[0];
var");
scripts.AppendFormat(@"
newScript = document.createElement('script');
newScript.type = 'text/javascript';
newScript.src = '{0}';
headID.appendChild(newScript);", personalizeJsLink);
string scriptBlock = scripts.ToString();
var existingActions = web.UserCustomActions;
ctx.Load(existingActions);
ctx.ExecuteQuery();
var actions = existingActions.ToArray();
foreach (var action in actions)
{
if (action.Description == "personalize" &&
action.Location == "ScriptLink")
{
action.DeleteObject();
ctx.ExecuteQuery();
}
}
var newAction = existingActions.Add();
newAction.Description = "personalize";
newAction.Location = "ScriptLink";
newAction.ScriptBlock = scriptBlock;
newAction.Update();
ctx.Load(web, s => s.UserCustomActions);
ctx.ExecuteQuery();
}
Dado que los sitios de equipo de SharePoint usan de forma predeterminada la estrategia de descarga mínima (MDS), el código del archivo personalize.js intenta registrarse primero con MDS. De este modo, al cargar la página que contiene JavaScript, el motor MDS inicia la función principal (RemoteManager_Inject). Si MDS está deshabilitado, esta función se inicia inmediatamente.
// Register script for MDS, if possible.
RegisterModuleInit("personalize.js", RemoteManager_Inject); //MDS registration
RemoteManager_Inject(); //non-MDS run
if (typeof (Sys) != "undefined" && Boolean(Sys) && Boolean(Sys.Application)) {
Sys.Application.notifyScriptLoaded();h
}
if (typeof (NotifyScriptLoadedAndExecuteWaitingJobs) == "function") {
NotifyScriptLoadedAndExecuteWaitingJobs("scenario1.js");
}
// The RemoteManager_Inject function is the entry point for loading the other scripts that perform the customizations. When a given script depends on another script, be sure to load the dependent script after the one on which it depends. This sample loads the JQuery library before the personalizeIt function that uses JQuery.
function RemoteManager_Inject() {
var jQuery = "https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.2.min.js";
// Load jQuery.
loadScript(jQuery, function () {
personalizeIt();
});
}
La función personalizeIt() comprueba el almacenamiento local HTML5 antes de buscar la información del perfil de usuario. Si va a la información del perfil de usuario, almacena la información que recupera en el almacenamiento local HTML5.
function personalizeIt() {
clientContext = SP.ClientContext.get_current();
var fileref = document.createElement('script');
fileref.setAttribute("type", "text/javascript");
fileref.setAttribute("src", "/_layouts/15/SP.UserProfiles.js");
document.getElementsByTagName("head")[0].appendChild(fileref);
SP.SOD.executeOrDelayUntilScriptLoaded(function () {
// Get localstorage values if they exist.
buCode = localStorage.getItem("bucode");
buCodeTimeStamp = localStorage.getItem("buCodeTimeStamp");
// Determine whether the page already has embedded personalized image.
var pageTitle = $('#pageTitle')[0].innerHTML;
if (pageTitle.indexOf("img") > -1) {
personalized = true;
}
else {
personalized = false;
}
// If nothing is in localstorage, get profile data, which will also populate localstorage.
if (buCode == "" || buCode == null) {
getProfileData(clientContext);
personalized = false;
}
else {
// Check for expiration.
if (isKeyExpired("buCodeTimeStamp")) {
getProfileData(clientContext);
if (buCode != "" || buCode != null) {
// Set timestamp for expiration.
currentTime = Math.floor((new Date().getTime()) / 1000);
localStorage.setItem("buCodeTimeStamp", currentTime);
// Set personalized to false so that the code can check for a new image in case buCode was updated.
personalized = false;
}
}
}
// Load image or make sure it is current based on the value in AboutMe.
if (!personalized) {
loadPersonalizedImage(buCode);
}
}, 'SP.UserProfiles.js');
}
El archivo personalize.js también contiene código que comprueba si la clave de almacenamiento local ha expirado. Esto no está integrado en el almacenamiento local HTML5.
// Check to see if the key has expired
function isKeyExpired(TimeStampKey) {
// Retrieve the example setting for expiration in seconds.
var expiryStamp = localStorage.getItem(TimeStampKey);
if (expiryStamp != null && cacheTimeout != null) {
// Retrieve the timestamp and compare against specified cache timeout settings to see if it is expired.
var currentTime = Math.floor((new Date().getTime()) / 1000);
if (currentTime - parseInt(expiryStamp) > parseInt(cacheTimeout)) {
return true; //Expired
}
else {
return false;
}
}
else {
//default
return true;
}
}
Representación del lado cliente
El ejemplo Branding.ClientSideRendering muestra cómo usar un complemento hospedado por el proveedor para aprovisionar de forma remota artefactos de SharePoint y archivos JSLink que usan la representación del lado cliente para personalizar el aspecto y el comportamiento de los campos de lista de SharePoint. Los archivos JSLink y la representación del lado cliente proporcionan control sobre cómo se representan los controles de una página de SharePoint (vistas de lista, campos de lista y agregar y editar formularios). Este control puede reducir o eliminar la necesidad de tipos de campo personalizados. La representación del lado cliente permite controlar de forma remota la apariencia del campo de lista de forma remota.
El ejemplo combina los ejemplos de JSLink de los ejemplos de código de representación del lado cliente (JSLink) en un único complemento hospedado por el proveedor para SharePoint que aprovisiona los archivos JSLink. La representación del lado cliente permite usar tecnologías web estándar, como HTML y JavaScript, para definir la lógica de representación de tipos de campo personalizados y predefinidos.
Al iniciar el ejemplo, la página de inicio le pide que aprovisione todos los ejemplos.
Página de inicio del ejemplo de representación del lado cliente
Al elegir Aprovisionar ejemplos, el complemento implementa una imagen, así como todas las listas de SharePoint, vistas de lista, elementos de lista, formularios y archivos JavaScript que se usan en cada ejemplo. El complemento crea una carpeta denominada JSLink-Samples en la biblioteca de estilos y, a continuación, carga los archivos de JavaScript en esa carpeta. El método UploadFileToFolder realiza el trabajo de carga y comprobación de cada archivo JavaScript.
public static void UploadFileToFolder(Web web, string filePath, Folder folder)
{
using (FileStream fs = new FileStream(filePath, FileMode.Open))
{
FileCreationInformation flciNewFile = new FileCreationInformation();
flciNewFile.ContentStream = fs;
flciNewFile.Url = System.IO.Path.GetFileName(filePath);
flciNewFile.Overwrite = true;
Microsoft.SharePoint.Client.File uploadFile = folder.Files.Add(flciNewFile);
uploadFile.CheckIn("CSR sample js file", CheckinType.MajorCheckIn);
folder.Context.Load(uploadFile);
folder.Context.ExecuteQuery();
}
}
Ejemplo 1: Aplicar formato a una columna de lista
En el ejemplo 1 se muestra cómo aplicar formato a una columna de lista en función del valor del campo. Los valores de campo de prioridad de 1 (Alto), 2 (Normal) y 3 (Bajo) se muestran en rojo, naranja y amarillo.
Presentación del campo de lista personalizada
El siguiente Código JavaScript invalida la presentación del campo predeterminado y crea una nueva plantilla para mostrar para el campo de lista Prioridad . La técnica de la función anónima que carga la información contextual sobre el campo cuya presentación desea invalidar se usa en todos los ejemplos.
(function () {
// Create object that has the context information about the field that you want to render differently.
var priorityFiledContext = {};
priorityFiledContext.Templates = {};
priorityFiledContext.Templates.Fields = {
// Apply the new rendering for Priority field in List View.
"Priority": { "View": priorityFiledTemplate }
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(priorityFiledContext);
})();
// This function provides the rendering logic for list view.
function priorityFiledTemplate(ctx) {
var priority = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
// Return HTML element with appropriate color based on the Priority column's value.
switch (priority) {
case "(1) High":
return "<span style='color :#f00'>" + priority + "</span>";
break;
case "(2) Normal":
return "<span style='color :#ff6a00'>" + priority + "</span>";
break;
case "(3) Low":
return "<span style='color :#cab023'>" + priority + "</span>";
}
}
Ejemplo 2: Acortar texto largo
En el ejemplo 2 se muestra cómo truncar el texto largo almacenado en el campo Cuerpo de una lista anuncios . El texto completo se muestra como un elemento emergente que aparece cada vez que se mantiene el puntero sobre un elemento de lista, como se muestra en la ilustración siguiente.
Visualización del campo de lista abreviada que muestra un elemento emergente
El siguiente Código JavaScript acorta el texto del campo Cuerpo y hace que el texto completo aparezca como un elemento emergente a través del atributo title en la etiqueta span .
function bodyFiledTemplate(ctx) {
var bodyValue = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
// This regex expression is used to delete HTML tags from the Body field.
var regex = /(<([^>]+)>)/ig;
bodyValue = bodyValue.replace(regex, "");
var newBodyValue = bodyValue;
if (bodyValue && bodyValue.length >= 100)
{
newBodyValue = bodyValue.substring(0, 100) + " ...";
}
return "<span title='" + bodyValue + "'>" + newBodyValue + "</span>";
}
Ejemplo 3: Mostrar una imagen con un nombre de documento
El ejemplo 3 muestra cómo mostrar una imagen junto a un nombre de documento dentro de una biblioteca de documentos. Aparece un distintivo rojo cada vez que el valor del campo Confidencial se establece en Sí.
Visualización de imagen junto al nombre de documento
El siguiente JavaScript comprueba el valor del campo Confidencial y, a continuación, personaliza la presentación del campo Nombre en función del valor de otro campo. En el ejemplo se usa la imagen que se carga al elegir Aprovisionar ejemplos.
function linkFilenameFiledTemplate(ctx) {
var confidential = ctx.CurrentItem["Confidential"];
var title = ctx.CurrentItem["FileLeafRef"];
// This Regex expression is used to delete the extension (.docx, .pdf ...) from the file name.
title = title.replace(/\.[^/.]+$/, "")
// Check confidential field value.
if (confidential && confidential.toLowerCase() == 'yes') {
// Render HTML that contains the file name and the confidential icon.
return title + "&nbsp;<img src= '" + _spPageContextInfo.siteServerRelativeUrl + "/Style%20Library/JSLink-Samples/imgs/Confidential.png' alt='Confidential Document' title='Confidential Document'/>";
}
else
{
return title;
}
}
Ejemplo 4: Mostrar un gráfico de barras
El ejemplo 4 muestra cómo mostrar un gráfico de barras en el campo % completado de una lista de tareas. La apariencia del gráfico de barras depende del valor del campo % completado , como se muestra en la ilustración siguiente. Tenga en cuenta que también aparece un gráfico de barras en los formularios para crear y editar elementos de lista de tareas.
Gráfico de barras que se muestra en el campo de % completado
El código siguiente crea la presentación del gráfico de barras y la asocia a los formularios de vista y presentación (percentCompleteViewFiledTemplate) y, a continuación, a los formularios nuevos y de edición (percentCompleteEditFiledTemplate).
// This function provides the rendering logic for View and Display forms.
function percentCompleteViewFiledTemplate(ctx) {
var percentComplete = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
return "<div style='background-color: #e5e5e5; width: 100px; display:inline-block;'> \
<div style='width: " + percentComplete.replace(/\s+/g, '') + "; background-color: #0094ff;'> \
&nbsp;</div></div>&nbsp;" + percentComplete;
}
// This function provides the rendering logic for New and Edit forms.
function percentCompleteEditFiledTemplate(ctx) {
var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
// Register a callback just before submit.
formCtx.registerGetValueCallback(formCtx.fieldName, function () {
return document.getElementById('inpPercentComplete').value;
});
return "<input type='range' id='inpPercentComplete' name='inpPercentComplete' min='0' max='100' \
oninput='outPercentComplete.value=inpPercentComplete.value' value='" + formCtx.fieldValue + "' /> \
<output name='outPercentComplete' for='inpPercentComplete' >" + formCtx.fieldValue + "</output>%";
}
Ejemplo 5: Cambio de la plantilla de representación
El ejemplo 5 muestra cómo cambiar la plantilla de representación de una vista de lista. Esta vista muestra los títulos de elementos de lista que se expanden como acordeones al seleccionarlos. La vista expandida muestra campos de elementos de lista adicionales.
Vistas de elemento de lista expandidas y contraídas
El código siguiente configura la plantilla y la registra con la plantilla de lista. Configura el diseño general y, a continuación, usa el controlador de eventos OnPostRender para registrar la función de JavaScript que se ejecuta cuando se representa la lista. Esta función asocia el evento con css y control de eventos que implementa la funcionalidad acordeón.
(function () {
// jQuery library is required in this sample.
// Fallback to loading jQuery from a CDN path if the local is unavailable.
(window.jQuery || document.write('<script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.10.0.min.js"><\/script>'));
// Create object that has the context information about the field that you want to render differently.
var accordionContext = {};
accordionContext.Templates = {};
// Be careful when adding the header for the template, because it will break the default list view render.
accordionContext.Templates.Header = "<div class='accordion'>";
accordionContext.Templates.Footer = "</div>";
// Add OnPostRender event handler to add accordion click events and style.
accordionContext.OnPostRender = accordionOnPostRender;
// This line of code tells the TemplateManager that you want to change all the HTML for item row rendering.
accordionContext.Templates.Item = accordionTemplate;
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(accordionContext);
})();
// This function provides the rendering logic.
function accordionTemplate(ctx) {
var title = ctx.CurrentItem["Title"];
var description = ctx.CurrentItem["Description"];
// Return whole item html.
return "<h2>" + title + "</h2><p>" + description + "</p><br/>";
}
function accordionOnPostRender() {
// Register event to collapse and expand when selecting accordion header.
$('.accordion h2').click(function () {
$(this).next().slideToggle();
}).next().hide();
$('.accordion h2').css('cursor', 'pointer');
}
Ejemplo 6: Validación de valores de campo
En el ejemplo 6 se muestra cómo usar expresiones regulares para validar los valores de campo proporcionados por el usuario. Aparece un mensaje de error rojo cuando el usuario escribe una dirección de correo electrónico no válida en el cuadro de texto Email campo. Esto sucede cuando el usuario crea o edita un elemento de lista.
Mensaje de error para entrada de texto de campo no válido
El código siguiente configura la plantilla con un marcador de posición para mostrar el mensaje de error y registra las funciones de devolución de llamada que se activan cada vez que el usuario intenta enviar los formularios. La primera devolución de llamada devuelve el valor de la columna Email y la segunda devolución de llamada usa expresiones regulares para validar el valor de cadena.
function emailFiledTemplate(ctx) {
var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
// Register a callback just before submit.
formCtx.registerGetValueCallback(formCtx.fieldName, function () {
return document.getElementById('inpEmail').value;
});
// Create container for various validations.
var validators = new SPClientForms.ClientValidation.ValidatorSet();
validators.RegisterValidator(new emailValidator());
// Validation failure handler.
formCtx.registerValidationErrorCallback(formCtx.fieldName, emailOnError);
formCtx.registerClientValidator(formCtx.fieldName, validators);
return "<span dir='none'><input type='text' value='" + formCtx.fieldValue + "' maxlength='255' id='inpEmail' class='ms-long'> \
<br><span id='spnError' class='ms-formvalidation ms-csrformvalidation'></span></span>";
}
// Custom validation object to validate email format.
emailValidator = function () {
emailValidator.prototype.Validate = function (value) {
var isError = false;
var errorMessage = "";
//Email format Regex expression
var emailRejex = /\S+@\S+\.\S+/;
if (!emailRejex.test(value) && value.trim()) {
isError = true;
errorMessage = "Invalid email address";
}
// Send error message to error callback function (emailOnError).
return new SPClientForms.ClientValidation.ValidationResult(isError, errorMessage);
};
};
// Add error message to spnError element under the input field element.
function emailOnError(error) {
document.getElementById("spnError").innerHTML = "<span role='alert'>" + error.errorMessage + "</span>";
}
Ejemplo 7: Hacer que los campos de edición de elementos de lista sean de solo lectura
El ejemplo siete muestra cómo hacer que los campos de formulario de edición de elementos de lista sean de solo lectura. Los campos de solo lectura se muestran sin controles de edición.
Campo de solo lectura en un formulario de edición de lista personalizado
En el ejemplo de código siguiente se modifican los campos Title, AssignedTo y Priority en el formulario de edición de elementos de lista para que muestren los valores de campo solo sin controles de edición. El código muestra cómo controlar los requisitos de análisis para diferentes tipos de campo.
function readonlyFieldTemplate(ctx) {
// Reuse SharePoint JavaScript libraries.
switch (ctx.CurrentFieldSchema.FieldType) {
case "Text":
case "Number":
case "Integer":
case "Currency":
case "Choice":
case "Computed":
return SPField_FormDisplay_Default(ctx);
case "MultiChoice":
prepareMultiChoiceFieldValue(ctx);
return SPField_FormDisplay_Default(ctx);
case "Boolean":
return SPField_FormDisplay_DefaultNoEncode(ctx);
case "Note":
prepareNoteFieldValue(ctx);
return SPFieldNote_Display(ctx);
case "File":
return SPFieldFile_Display(ctx);
case "Lookup":
case "LookupMulti":
return SPFieldLookup_Display(ctx);
case "URL":
return RenderFieldValueDefault(ctx);
case "User":
prepareUserFieldValue(ctx);
return SPFieldUser_Display(ctx);
case "UserMulti":
prepareUserFieldValue(ctx);
return SPFieldUserMulti_Display(ctx);
case "DateTime":
return SPFieldDateTime_Display(ctx);
case "Attachments":
return SPFieldAttachments_Default(ctx);
case "TaxonomyFieldType":
//Re-use JavaScript from the sp.ui.taxonomy.js SharePoint JavaScript library.
return SP.UI.Taxonomy.TaxonomyFieldTemplate.renderDisplayControl(ctx);
}
}
// User control needs specific formatted value to render content correctly.
function prepareUserFieldValue(ctx) {
var item = ctx['CurrentItem'];
var userField = item[ctx.CurrentFieldSchema.Name];
var fieldValue = "";
for (var i = 0; i < userField.length; i++) {
fieldValue += userField[i].EntityData.SPUserID + SPClientTemplates.Utility.UserLookupDelimitString + userField[i].DisplayText;
if ((i + 1) != userField.length) {
fieldValue += SPClientTemplates.Utility.UserLookupDelimitString
}
}
ctx["CurrentFieldValue"] = fieldValue;
}
// Choice control needs specific formatted value to render content correctly.
function prepareMultiChoiceFieldValue(ctx) {
if (ctx["CurrentFieldValue"]) {
var fieldValue = ctx["CurrentFieldValue"];
var find = ';#';
var regExpObj = new RegExp(find, 'g');
fieldValue = fieldValue.replace(regExpObj, '; ');
fieldValue = fieldValue.replace(/^; /g, '');
fieldValue = fieldValue.replace(/; $/g, '');
ctx["CurrentFieldValue"] = fieldValue;
}
}
// Note control needs specific formatted value to render content correctly.
function prepareNoteFieldValue(ctx) {
if (ctx["CurrentFieldValue"]) {
var fieldValue = ctx["CurrentFieldValue"];
fieldValue = "<div>" + fieldValue.replace(/\n/g, '<br />'); + "</div>";
ctx["CurrentFieldValue"] = fieldValue;
}
}
Ejemplo 8: Ocultar campos
En el ejemplo 8 se muestra cómo ocultar campos en formularios de edición y nuevos elementos de lista. El ejemplo oculta el campo Predecesores cuando un usuario crea o edita un elemento de lista de tareas.
Este ejemplo se implementa como edición y nuevo formulario para una lista denominada CSR-Hide-Controls list. Para obtener información sobre cómo ver el formulario después de implementar el ejemplo, vea Branding.ClientSideRendering.
El código siguiente busca el campo Predecesores en el HTML del formulario y lo oculta. El campo permanece presente en el HTML, pero el usuario no puede verlo en el explorador.
(function () {
// jQuery library is required in this sample.
// Fallback to loading jQuery from a CDN path if the local is unavailable.
(window.jQuery || document.write('<script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.10.0.min.js"><\/script>'));
// Create object that has the context information about the field that we want to render differently.
var hiddenFiledContext = {};
hiddenFiledContext.Templates = {};
hiddenFiledContext.Templates.OnPostRender = hiddenFiledOnPreRender;
hiddenFiledContext.Templates.Fields = {
// Apply the new rendering for Predecessors field in New and Edit forms.
"Predecessors": {
"NewForm": hiddenFiledTemplate,
"EditForm": hiddenFiledTemplate
}
};
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(hiddenFiledContext);
})();
// This function provides the rendering logic.
function hiddenFiledTemplate() {
return "<span class='csrHiddenField'></span>";
}
// This function provides the rendering logic.
function hiddenFiledOnPreRender(ctx) {
jQuery(".csrHiddenField").closest("tr").hide();
}
Manipulación de elementos web y elementos de complemento
En el ejemplo Core.AppScriptPart se muestra cómo usar elementos de script de complemento para insertar scripts que se ejecutan en un complemento hospedado por el proveedor en una página de SharePoint. En este ejemplo se muestra cómo modificar la interfaz de usuario de una página en el sitio host implementando un elemento de script de complemento y agregándolo a una página de SharePoint desde la Galería de elementos web.
Un elemento de script de complemento es como un elemento web en el que puede agregarlo a una página de SharePoint desde la Galería de elementos web, pero en este caso el archivo .webpart inserta un archivo JavaScript que se ejecuta de forma remota en un complemento hospedado por el proveedor. El elemento de script del complemento se ejecuta dentro de una <div>
etiqueta en la página de SharePoint y, por lo tanto, proporciona un diseño y una experiencia con mayor capacidad de respuesta de lo que se obtiene con los elementos de complemento que se ejecutan en IFrames.
La página de inicio incluye un botón Ejecutar escenario que implementa el elemento de script del complemento en la Galería de elementos web. En el ejemplo de código siguiente se crea una instancia de FileCreationInformationObject que contiene el contenido del archivo .webpart y, a continuación, se carga el nuevo archivo en la Galería de elementos web. Tenga en cuenta que también puede ejecutar este código automáticamente cuando el elemento de complemento se instala o como parte del proceso de aprovisionamiento de la colección de sitios.
var spContext = SharePointContextProvider.Current.GetSharePointContext(Context);
using (var clientContext = spContext.CreateUserClientContextForSPHost())
{
var folder = clientContext.Web.Lists.GetByTitle("Web Part Gallery").RootFolder;
clientContext.Load(folder);
clientContext.ExecuteQuery();
// Upload the OneDrive for Business Usage Guidelines.docx.
using (var stream = System.IO.File.OpenRead(Server.MapPath("~/userprofileinformation.webpart")))
{
FileCreationInformation fileInfo = new FileCreationInformation();
fileInfo.ContentStream = stream;
fileInfo.Overwrite = true;
fileInfo.Url = "userprofileinformation.webpart";
File file = folder.Files.Add(fileInfo);
clientContext.ExecuteQuery();
}
// Update the group for uploaded web part.
var list = clientContext.Web.Lists.GetByTitle("Web Part Gallery");
CamlQuery camlQuery = CamlQuery.CreateAllItemsQuery(100);
Microsoft.SharePoint.Client.ListItemCollection items = list.GetItems(camlQuery);
clientContext.Load(items);
clientContext.ExecuteQuery();
foreach (var item in items)
{
// Random group name to differentiate it from the rest.
if (item["FileLeafRef"].ToString().ToLowerInvariant() == "userprofileinformation.webpart")
{
item["Group"] = "add-in Script Part";
item.Update();
clientContext.ExecuteQuery();
}
}
lblStatus.Text = string.Format("add-in script part has been added to Web Part Gallery. You can find 'User Profile Information' script part under 'Add-in Script Part' group in the <a href='{0}'>host web</a>.", spContext.SPHostUrl.ToString());
}
Después de finalizar este paso, puede encontrar el elemento de script de información de perfil de usuario dentro de una nueva categoría de elemento de script de complemento en la Galería de elementos web. Después de agregar el elemento de script del complemento a la página, javaScript que se ejecuta de forma remota controla la visualización de la información en la página.
Cuando vea el elemento de script del complemento en modo de edición, verá que inserta el archivo JavaScript que se ejecuta de forma remota. El script deuserprofileinformation.js usa json para obtener información de perfil de usuario del sitio host.
function sharePointReady() {
clientContext = SP.ClientContext.get_current();
var fileref = document.createElement('script');
fileref.setAttribute("type", "text/javascript");
fileref.setAttribute("src", "/_layouts/15/SP.UserProfiles.js");
document.getElementsByTagName("head")[0].appendChild(fileref);
SP.SOD.executeOrDelayUntilScriptLoaded(function () {
//Get Instance of People Manager Class.
var peopleManager = new SP.UserProfiles.PeopleManager(clientContext);
//Get properties of the current user.
userProfileProperties = peopleManager.getMyProperties();
clientContext.load(userProfileProperties);
clientContext.executeQueryAsync(Function.createDelegate(this, function (sender, args) {
var firstname = userProfileProperties.get_userProfileProperties()['FirstName'];
var name = userProfileProperties.get_userProfileProperties()['PreferredName'];
var title = userProfileProperties.get_userProfileProperties()['Title'];
var aboutMe = userProfileProperties.get_userProfileProperties()['AboutMe'];
var picture = userProfileProperties.get_userProfileProperties()['PictureURL'];
var html = "<div><h2>Welcome " + firstname + "</h2></div><div><div style='float: left; margin-left:10px'><img style='float:left;margin-right:10px' src='" + picture + "' /><b>Name</b>: " + name + "<br /><b>Title</b>: " + title + "<br />" + aboutMe + "</div></div>";
document.getElementById('UserProfileAboutMe').innerHTML = html;
}), Function.createDelegate(this, function (sender, args) {
console.log('The following error has occurred while loading user profile property: ' + args.get_message());
}));
}, 'SP.UserProfiles.js');
}
Aprovisionamiento de características de publicación
El ejemplo Provisioning.PublishingFeatures muestra cómo realizar tareas comunes con sitios de publicación hospedados en Office 365; por ejemplo, aprovisionar y usar diseños de página, páginas maestras y temas, o insertar JavaScript en diseños de página. También se muestra cómo aplicar filtros que controlan qué plantillas de sitio están disponibles para los subsitios y qué diseños de página están disponibles en la web host.
El complemento hospedado por el proveedor usa CSOM para aprovisionar elementos de interfaz de usuario de uso común en sitios de publicación y usa JavaScript para crear experiencias más dinámicas en los diseños de página que se pueden implementar en sitios de publicación. También muestra las diferencias entre el uso de páginas maestras y temas en sitios de publicación.
Importante
Para que la funcionalidad de este ejemplo funcione, debe activar las características de publicación en el sitio. Para obtener información, consulte Habilitación de características de publicación.
La página de inicio de ejemplo le presenta tres escenarios para personalizar la interfaz de usuario de los sitios de publicación:
- Implementar diseños de página.
- Implementar temas y páginas maestras.
- Filtre los diseños de página y las plantillas de sitio disponibles en el sitio host.
Escenario 1: Implementación de páginas
En el escenario 1 se muestra cómo implementar un diseño de página personalizado. El botón Implementar diseños de página que se muestra en la ilustración siguiente crea un nuevo diseño de página y una página que usa ese diseño.
Botón para implementar diseños de página
Para ver la nueva página, vaya a la página de demostración recién creada en el sitio host, que contiene JavaScript incrustado y muestra información de perfil de usuario.
En el ejemplo se muestra la información de este usuario en la página. También agrega una página, aunque en este caso usa un objeto PublishingPageInformation para crear la nueva página.
El ejemplo agrega un nuevo diseño de página al cargar un archivo en la Galería de páginas maestras y asignarle el tipo de contenido de diseño de página. El código siguiente toma la ruta de acceso a un archivo *.aspx (que puede implementar como un recurso en el proyecto de Visual Studio) y lo agrega como un diseño de página en la Galería de páginas maestras.
// Get the path to the file that you are about to deploy.
List masterPageGallery = web.GetCatalog((int)ListTemplateType.MasterPageCatalog);
Folder rootFolder = masterPageGallery.RootFolder;
web.Context.Load(masterPageGallery);
web.Context.Load(rootFolder);
web.Context.ExecuteQuery();
var fileBytes = System.IO.File.ReadAllBytes(sourceFilePath);
// Use CSOM to upload the file.
FileCreationInformation newFile = new FileCreationInformation();
newFile.Content = fileBytes;
newFile.Url = UrlUtility.Combine(rootFolder.ServerRelativeUrl, fileName);
newFile.Overwrite = true;
Microsoft.SharePoint.Client.File uploadFile = rootFolder.Files.Add(newFile);
web.Context.Load(uploadFile);
web.Context.ExecuteQuery();
// Check out the file if needed.
if (masterPageGallery.ForceCheckout || masterPageGallery.EnableVersioning)
{
if (uploadFile.CheckOutType == CheckOutType.None)
{
uploadFile.CheckOut();
}
}
// Get content type for ID to assign associated content type information.
ContentType associatedCt = web.GetContentTypeById(associatedContentTypeID);
var listItem = uploadFile.ListItemAllFields;
listItem["Title"] = title;
listItem["MasterPageDescription"] = description;
// Set the item as page layout.
listItem["ContentTypeId"] = Constants.PAGE_LAYOUT_CONTENT_TYPE;
// Set the associated content type ID property
listItem["PublishingAssociatedContentType"] = string.Format(";#{0};#{1};#", associatedCt.Name, associatedCt.Id);
listItem["UIVersion"] = Convert.ToString(15);
listItem.Update();
// Check in the page layout if needed.
if (masterPageGallery.ForceCheckout || masterPageGallery.EnableVersioning)
{
uploadFile.CheckIn(string.Empty, CheckinType.MajorCheckIn);
listItem.File.Publish(string.Empty);
}
web.Context.ExecuteQuery();
Para comprobar que la nueva página usa el nuevo diseño de página, vaya a la biblioteca Pages del sitio host.
Escenario 2: Implementación de temas y páginas maestras
En el escenario 2 se muestra cómo implementar y establecer páginas maestras y temas para el sitio host desde un complemento hospedado por el proveedor. Al elegir Implementar patrón y usarlo en la página de inicio de ejemplo, el ejemplo implementa y aplica una página maestra personalizada al sitio host. Para ver la nueva página maestra, vaya a la página principal del sitio.
El ejemplo agrega una nueva página maestra cargando un archivo *.master en la Galería de páginas maestras y asignándole el tipo de contenido de página maestra. El código siguiente toma la ruta de acceso a un archivo *.master (que puede implementar como un recurso en el proyecto de Visual Studio) y lo agrega como una página maestra en la Galería de páginas maestras.
string fileName = Path.GetFileName(sourceFilePath);
// Get the path to the file that you are about to deploy.
List masterPageGallery = web.GetCatalog((int)ListTemplateType.MasterPageCatalog);
Folder rootFolder = masterPageGallery.RootFolder;
web.Context.Load(masterPageGallery);
web.Context.Load(rootFolder);
web.Context.ExecuteQuery();
// Get the file name from the provided path.
var fileBytes = System.IO.File.ReadAllBytes(sourceFilePath);
// Use CSOM to upload the file.
FileCreationInformation newFile = new FileCreationInformation();
newFile.Content = fileBytes;
newFile.Url = UrlUtility.Combine(rootFolder.ServerRelativeUrl, fileName);
newFile.Overwrite = true;
Microsoft.SharePoint.Client.File uploadFile = rootFolder.Files.Add(newFile);
web.Context.Load(uploadFile);
web.Context.ExecuteQuery();
var listItem = uploadFile.ListItemAllFields;
if (masterPageGallery.ForceCheckout || masterPageGallery.EnableVersioning)
{
if (uploadFile.CheckOutType == CheckOutType.None)
{
uploadFile.CheckOut();
}
}
listItem["Title"] = title;
listItem["MasterPageDescription"] = description;
// Set content type as master page.
listItem["ContentTypeId"] = Constants.MASTERPAGE_CONTENT_TYPE;
listItem["UIVersion"] = uiVersion;
listItem.Update();
if (masterPageGallery.ForceCheckout || masterPageGallery.EnableVersioning)
{
uploadFile.CheckIn(string.Empty, CheckinType.MajorCheckIn);
listItem.File.Publish(string.Empty);
}
web.Context.Load(listItem);
web.Context.ExecuteQuery();
El siguiente paso consiste en establecer la dirección URL de la nueva página maestra como el valor de las propiedades MasterUrl y CustomMasterUrl del objeto Web que representa el sitio. El ejemplo controla esto con un único método que captura la dirección URL de la nueva página maestra en la Galería de páginas maestras y, a continuación, asigna ese valor a las propiedades Web.MasterUrl y Web.CustomMasterUrl .
// Assign master page to the host web.
clientContext.Web.SetMasterPagesForSiteByName("contoso.master", "contoso.master");
Al elegir Implementar tema y usarlo, el ejemplo implementa y aplica un tema personalizado al sitio host. El ejemplo establece la paleta de colores, la imagen de fondo y la combinación de fuentes del tema agregando un nuevo tema con esos valores (que puede implementar como recursos dentro del proyecto de Visual Studio) a la Galería de temas. El código siguiente crea el nuevo tema.
List themesOverviewList = web.GetCatalog((int)ListTemplateType.DesignCatalog);
web.Context.Load(themesOverviewList);
web.Context.ExecuteQuery();
ListItemCreationInformation itemInfo = new ListItemCreationInformation();
Microsoft.SharePoint.Client.ListItem item = themesOverviewList.AddItem(itemInfo);
item["Name"] = themeName;
item["Title"] = themeName;
if (!string.IsNullOrEmpty(colorFileName))
{
item["ThemeUrl"] = UrlUtility.Combine(rootWeb.ServerRelativeUrl, string.Format(Constants.THEMES_DIRECTORY, Path.GetFileName(colorFileName)));
}
if (!string.IsNullOrEmpty(fontFileName))
{
item["FontSchemeUrl"] = UrlUtility.Combine(rootWeb.ServerRelativeUrl, string.Format(Constants.THEMES_DIRECTORY, Path.GetFileName(fontFileName)));
}
if (!string.IsNullOrEmpty(backgroundName))
{
item["ImageUrl"] = UrlUtility.Combine(rootWeb.ServerRelativeUrl, string.Format(Constants.THEMES_DIRECTORY, Path.GetFileName(backgroundName)));
}
item["DisplayOrder"] = 11;
item.Update();
web.Context.ExecuteQuery();
El siguiente paso es establecer este nuevo tema como tema para el sitio. Para ello, el código siguiente captura el tema de la Galería de temas y, a continuación, aplica sus valores al sitio host.
CamlQuery query = new CamlQuery();
// Find the theme by themeName.
string camlString = string.Format(CAML_QUERY_FIND_BY_FILENAME, themeName);
query.ViewXml = camlString;
var found = themeList.GetItems(query);
rootWeb.Context.Load(found);
LoggingUtility.Internal.TraceVerbose("Getting theme: {0}", themeName);
rootWeb.Context.ExecuteQuery();
if (found.Count > 0)
{
ListItem themeEntry = found[0];
// set the properties for applying the custom theme that was just uploaded.
string spColorURL = null;
if (themeEntry["ThemeUrl"] != null && themeEntry["ThemeUrl"].ToString().Length > 0)
{
spColorURL = UrlUtility.MakeRelativeUrl((themeEntry["ThemeUrl"] as FieldUrlValue).Url);
}
string spFontURL = null;
if (themeEntry["FontSchemeUrl"] != null && themeEntry["FontSchemeUrl"].ToString().Length > 0)
{
spFontURL = UrlUtility.MakeRelativeUrl((themeEntry["FontSchemeUrl"] as FieldUrlValue).Url);
}
string backGroundImage = null;
if (themeEntry["ImageUrl"] != null && themeEntry["ImageUrl"].ToString().Length > 0)
{
backGroundImage = UrlUtility.MakeRelativeUrl((themeEntry["ImageUrl"] as FieldUrlValue).Url);
}
// Set theme for demonstration.
// TODO: Why is shareGenerated false? If deploying to root and inheriting, maybe use shareGenerated = true.
web.ApplyTheme(spColorURL,
spFontURL,
backGroundImage,
false);
web.Context.ExecuteQuery();
Escenario 3: Filtrar diseños de página disponibles y plantillas de sitio
En el escenario 3 se muestra cómo limitar las opciones que tienen los usuarios cuando aplican plantillas a nuevos sitios y diseños a páginas nuevas. Al elegir Aplicar filtros a la web de host en la página de inicio de ejemplo, el ejemplo establece un diseño de página personalizado como predeterminado y un diseño de página adicional como la única opción para las páginas nuevas que crea un usuario. El ejemplo también reduce el número de opciones disponibles para los usuarios al crear nuevos subsitios. En la ilustración siguiente se muestra el aspecto del cuadro de selección de plantilla de sitio antes y después de que se hayan aplicado los filtros.
Selección de plantilla de sitio antes y después de que se han aplicado filtros de ejemplo
En el ejemplo se establecen los diseños de página predeterminados y disponibles pasando los archivos *.aspx asociados a los métodos de extensión, como se muestra en el código.
List<string> pageLayouts = new List<string>();
pageLayouts.Add("ContosoLinksBelow.aspx");
pageLayouts.Add("ContosoLinksRight.aspx");
clientContext.Web.SetAvailablePageLayouts(clientContext.Web, pageLayouts);
// Set default page layout for the site.
clientContext.Web.SetDefaultPageLayoutForSite(clientContext.Web, "ContosoLinksBelow.aspx");
El ejemplo establece las plantillas de sitio disponibles haciendo algo similar. En este caso, pasa las instancias de WebTemplateEntity que definen cada plantilla de sitio a un método de extensión denominado SetAvailableWebTemplates.
List<WebTemplateEntity> templates = new List<WebTemplateEntity>();
templates.Add(new WebTemplateEntity() { LanguageCode = "1035", TemplateName = "STS#0" });
templates.Add(new WebTemplateEntity() { LanguageCode = "", TemplateName = "STS#0" });
templates.Add(new WebTemplateEntity() { LanguageCode = "", TemplateName = "BLOG#0" });
clientContext.Web.SetAvailableWebTemplates(templates);
Los tres métodos de extensión (SetAvailablePageLayouts, SetDefaultPageLayoutForSite y SetAvailableWebTemplates) funcionan de la misma manera. Crean documentos XML que contienen pares clave-valor que definen los diseños disponibles y predeterminados y las plantillas disponibles. Después, pasan estos documentos a un método de extensión adicional denominado SetPropertyBagValue. Este método se implementa en la extensión OfficeDevPnP Core. Después de configurar los contenedores de propiedades adecuados, estos contenedores de propiedades se usan para filtrar las opciones en la interfaz.
De los tres métodos, SetAvailableWebTemplates muestra el patrón completo.
public static void SetAvailableWebTemplates(this Web web, List<WebTemplateEntity> availableTemplates)
{
string propertyValue = string.Empty;
LanguageTemplateHash languages = new LanguageTemplateHash();
foreach (var item in availableTemplates)
{
AddTemplateToCollection(languages, item);
}
if (availableTemplates.Count > 0)
{
XmlDocument xd = new XmlDocument();
XmlNode xmlNode = xd.CreateElement("webtemplates");
xd.AppendChild(xmlNode);
foreach (var language in languages)
{
XmlNode xmlLcidNode = xmlNode.AppendChild(xd.CreateElement("lcid"));
XmlAttribute xmlAttribute = xd.CreateAttribute("id");
xmlAttribute.Value = language.Key;
xmlLcidNode.Attributes.SetNamedItem(xmlAttribute);
foreach (string item in language.Value)
{
XmlNode xmlWTNode = xmlLcidNode.AppendChild(xd.CreateElement("webtemplate"));
XmlAttribute xmlAttributeName = xd.CreateAttribute("name");
xmlAttributeName.Value = item;
xmlWTNode.Attributes.SetNamedItem(xmlAttributeName);
}
}
propertyValue = xmlNode.OuterXml;
}
// Save the XML entry to property bag.
web.SetPropertyBagValue(AvailableWebTemplates, propertyValue);
// Set that templates are not inherited.
web.SetPropertyBagValue(InheritWebTemplates, "False");
}
El contenedor de propiedades InheritWebTemplates garantiza que las plantillas que normalmente se heredan del sitio primario también se omiten al crear subsitios.