SSD: el síndrome de la Sharepoint dependencia

Sharepoint me proporciona seguridad y me hace sentir más fuerte. Las 10 cosas que más me gustan de Sharepoint.

10 puntos para entender a Project Server 2010

Microsoft Project es quizá la herramienta de gestión de proyectos más conocida y utilizada por los líderes de proyectos...

Diseño Gráfico en SharePoint

Serie de artìculos que nos ayudan a incorporar diseño gráfico en las implementaciones de SharePoint...

Revista CompartiMOSS

Artículos publicados en la revista especializada en SharePoint: CompartiMOSS.

Contacto

Enviame un correo :-)

jueves, 19 de noviembre de 2015

¿Cómo crear una vista de hoja de datos y ocultarla en c# en SharePoint 2010?

SPList listaRiesgos = oWebsite.Lists.TryGetList("Issues y riesgos");
System.Collections.Specialized.StringCollection columnas = new System.Collections.Specialized.StringCollection();
columnas.Add("Title");
columnas.Add("Observaciones");
string query = "<Where><And><Neq><FieldRef Name='Estado_Cumplimiento_Riesgo'/><Value Type='Text'>Cerrado</Value></Neq><Neq><FieldRef Name='Estado_Cumplimiento_Riesgo'/><Value Type='Text'>Desestimado</Value></Neq></And></Where><OrderBy><FieldRef Name='Tipo_Riesgo' Ascending='False'/></OrderBy>";
SPView vista = listaRiesgos.Views.Add("mivista", columnas, query, 100, true, false, SPViewCollection.SPViewType.Grid, false);
vista.Hidden = true;
vista.Update();


lunes, 16 de noviembre de 2015

La foto del anillo especial por los cinco años como MVP!

domingo, 25 de octubre de 2015

¿Cómo listas los nombres internos de las columnas de una lista en SharePoint con PowerShell?

$lista = $web.Lists["Issues y riesgos"]
$lista.Fields | Select Title, InternalName | sort InternalName | out-file c:\nombres.txt
NotePad c:\nombres.txt


jueves, 8 de octubre de 2015

Stack Overflow (in Spanish)

jueves, 1 de octubre de 2015

Quinto año de MVP :-)

Muy contento :-)


miércoles, 23 de septiembre de 2015

¿Cómo eliminar manejadores de eventos huérfanos con C# en SharePoint?

Arquitectura de la solución
  • Un sitio raíz
  • Varios sitios hijos
  • Tipos de contenido en el sitio raíz
  • Listas en los sitios hijos que consumen los tipos de contenido del sitio raíz
  • Manejadores de eventos asociados a los tipos de contenido del sitio raíz en forma programática:



El problema

Se presenta un error al tratar de eliminar los eventos asociados el tipo de contenido, propagando el cambio hacia los tipos de contenido descendientes:




El error

Mensaje: Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. Operación no válida dado el estado actual del objeto. 

Stack trace: en Microsoft.SharePoint.SPContentType.PushDownChanges(CodeToPushDownChangesToDerivedCT derivedCTPushdownCode, CodeToPushDownChangesToListCTs listDerivedCTsPushdownCode, Boolean throwOnSealedOrReadOnly, IList`1 exceptions) en Microsoft.SharePoint.SPContentType.UpdateOnWeb(Boolean bPushdown, Boolean ignoreSealedOrReadOnly, Boolean bThrowOnSealedOrReadOnly, Boolean bSetNextChildByteToZero, IList`1 exceptions) en Microsoft.SharePoint.SPContentType.Update(Boolean updateChildren, Boolean ignoreSealedOrReadOnly, Boolean throwOnSealedOrReadOnly, IList`1 exceptions) en Microsoft.SharePoint.SPContentType.Update(Boolean updateChildren, Boolean throwOnSealedOrReadOnly) en EMP_Procesos.Layouts.EMP_Procesos.Upgrade.asociarEventosGovernance(SPSite sitio) en EMP_Procesos.Layouts.EMP_Procesos.Upgrade.b__2()

Source: Microsoft.SharePoint

Target site: Void PushDownChanges(CodeToPushDownChangesToDerivedCT, CodeToPushDownChangesToListCTs, Boolean, System.Collections.Generic.IList`1[System.Exception])



El análisis del problema

Investigando con SharePoint Manager, se observa que hay tipos de contenido en los sitios hijos que tienen un ensamblado erróneo y no tienen los manejadores de eventos. Se observa que la rama de EventReceivers no se puede abrir:



Debería verse así (sitios en que se encuentra bien)



La solución:

Se busca eliminar los manejadores de eventos dentro del XML utilizando este código proporcionado en una respuesta de Stackoverflow:

http://sharepoint.stackexchange.com/a/47408/39542 (se recomienda ver el hilo completo ya que existen varias soluciones que dependen de cada escenario)

var ctG = listaG.ContentTypes["Governance"];
if (ctG.XmlDocuments.Count > 0)
{
  ctG.XmlDocuments.Delete("http://schemas.microsoft.com/sharepoint/events");
  contenido.InnerHtml += "<span>" + oWebsite.Title + ": XML Documents encontrados en Governance</span><br>";
  ctG.Update();
}

El arreglo

Los tipos de contenido se visualizan así en SharePoint Manager luego de la "limpieza"




El resultado

Luego de borrar y volver a asociar los eventos, se observa el XML con el ensamblado correcto y los eventos correctos asociados:


lunes, 21 de septiembre de 2015

¿Cómo listas los manejadores de eventos de los tipos de contenido del sitio raíz en SharePoint con C#?

SPContentTypeCollection tcs = oSiteCollection.RootWeb.ContentTypes;
foreach (SPContentType tc in tcs)
{
  contenido.InnerHtml += "<span><b>" + tc.Name + "</b></span><br>";
  SPEventReceiverDefinitionCollection eventos = tc.EventReceivers;
  foreach (SPEventReceiverDefinition evento in eventos)
  {
    contenido.InnerHtml += "<span>" + evento.Synchronization + "</span><br>";
    contenido.InnerHtml += "<span>" + evento.Class + "</span><br>";
    contenido.InnerHtml += "<span>" + evento.Type + "</span><br>";
  }
}



lunes, 17 de agosto de 2015

¿Cómo leer un ítem con buena performance de una lista en SharePoint con C#?

La recomendación es usar GetItemByIdSelectedFields y especificar sólo los elementos que necesitamos. Recordar usar el internal name.

using (SPWeb web = SPContext.Current.Web)
{
  string urlRiesgos = (web.ServerRelativeUrl +  "/Lists/Riesgos").Replace("//","/");
  SPList listaRiesgos = web.GetList(urlRiesgos);
  SPListItem itemRiesgo = listaRiesgos.GetItemByIdSelectedFields(idRiesgo, "Estado_Cumplimiento_Riesgo", "Tipo_Riesgo");
  if (itemRiesgo["Estado"] != null)
    estadoActual = itemRiesgo["Estado"].ToString();
  tipoRiesgo = itemRiesgo["Tipo_Riesgo"].ToString();
}

domingo, 16 de agosto de 2015

¿Cómo especificar un valor nulo como default de una columna de sitio choice en SharePoint con C#?

SPFieldChoice estadoRiesgo = (SPFieldChoice)oSiteCollection.RootWeb.Fields.GetFieldByInternalName("Estado_Cumplimiento_Riesgo");
if (estadoRiesgo.DefaultValue != null)
{
  estadoRiesgo.DefaultValue = null;
  estadoRiesgo.Update(true);
}

¿Cómo inhabilitar la edición en hoja de datos de una lista en SharePoint con C#?

SPList listaRiesgos = oWebsite.Lists.TryGetList("Issues y riesgos");
if (listaRiesgos != null)
{
  if (!listaRiesgos.DisableGridEditing)
  {
    listaRiesgos.DisableGridEditing = true;
    contenido.InnerHtml += "OK";
  }
  else
    contenido.InnerHtml += "NO-OK";
}

viernes, 14 de agosto de 2015

¿Cómo hacer no visible un campo en las pantallas de edición de lista en SharePoint con C#?

        // Modificar visibilidad de columna de sitio
        public string alterarVisibilidadColumnaSitio(SPSite sitio, string columna, bool showNew, bool showEdit, bool showDisp)
        {
            SPField columnaSitio = sitio.RootWeb.Fields.GetFieldByInternalName(columna);
            if (columnaSitio.ShowInNewForm == showNew && columnaSitio.ShowInEditForm == showEdit && columnaSitio.ShowInDisplayForm == showDisp )
                return "<span style='color: blue'>Columna " + columna + " no requiere modificación de visibilidad.</span><br>";
            else
            {
                columnaSitio.ShowInNewForm = showNew;
                columnaSitio.ShowInEditForm = showEdit;
                columnaSitio.ShowInDisplayForm = showDisp;
                columnaSitio.PushChangesToLists = true;
                columnaSitio.Update(true);
                return "<span style='color: green'>Columna " + columna + ": visibilidad modificada.</span><br>";
            }
        }

En la siguientes imágenes pueden ver la diferencia entre la pantalla de alta y la de edición:






miércoles, 12 de agosto de 2015

¿Cómo crear un custom job para una colección de sitios en particular en SharePoint con C#?

En este artículo veremos como crear un job personalizado en SharePoint que sea específico de una colección de sitios. Esto lo haremos sin "hardcodear" la URL ni almacenarla en alguna opción de configuración.

Para ello seguimos los siguientes lineamientos:
  • Creamos una feature de scope SITE
  • Guardamos la URL de la colección del sitio en forma dinámica en el momento de crear el job
  • Leemos la URL guardada en el momento de ejecutar el job

Paso 1: feature

Creamos una feature de scope SITE.



Y agregamos eventos para activarla y desactivarla

using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.Administration;

namespace EMP_Procesos.Features.EMP_Alarmas
{

    [Guid("blabla")]
    public class EMP_AlarmasEventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                SPSite sitio = properties.Feature.Parent as SPSite;
                SPWebApplication webApp = sitio.WebApplication;
                EMPJobs.alarmasJobAdm.borrarJobAlarmas(webApp.JobDefinitions);
                EMPJobs.alarmasJobAdm.crearJobAlarmas(webApp, sitio.Url);
            });

        }
        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                SPSite sitio = properties.Feature.Parent as SPSite;
                SPWebApplication webApp = sitio.WebApplication;
                EMPJobs.alarmasJobAdm.borrarJobAlarmas(webApp.JobDefinitions);
            });
        }
    }
}


Paso 2: Creación del job

Desde la feature se llama a las funciones que crean y borran el job. Fíjense en particular que la función que crea el job almacena la propiedad con la URL del site collection:

    public class EMPJobs
    {

        // Lógica del JOB


        // Administración del JOB

        public static class alarmasJobAdm
        {

            public static void crearJobAlarmas(SPWebApplication webApp, string url)
            {
                EMPJobs.alarmasJob alarmasTimerJob = new EMPJobs.alarmasJob(webApp);

                // Guardo la URL del site collection como propiedad
                if (alarmasTimerJob.Properties.ContainsKey("sitioJobUrl"))
                    alarmasTimerJob.Properties.Remove("sitioJobUrl");
                alarmasTimerJob.Properties.Add("sitioJobUrl", url);

                // Schedule
                SPMinuteSchedule schedule = new SPMinuteSchedule();
                schedule.BeginSecond = 0;
                schedule.Interval = 30;
                alarmasTimerJob.Schedule = schedule;

                alarmasTimerJob.Update();
            }

            public static void borrarJobAlarmas(SPJobDefinitionCollection jobs)
            {
                foreach (SPJobDefinition job in jobs)
                    if (job.Name.Equals(EMPJobs.alarmasJob.nombreJob, StringComparison.OrdinalIgnoreCase))
                        job.Delete();
            }
        }

    }


Paso 3: Ejecución del job

    public class EMPJobs
    {

        // Lógica del JOB

        public class alarmasJob : SPJobDefinition
        {
            public const string nombreJob = "SURPOINT_Alarmas";
            
            public alarmasJob() : base() { }
            
            public alarmasJob(SPWebApplication webApplication) : base(nombreJob, webApplication, null, SPJobLockType.Job)
            {
                this.Title = "SURPOINT - Sistema de alarmas";
            }
            
            public override void Execute(Guid targetInstanceId)
            {
                SPSecurity.RunWithElevatedPrivileges(delegate()
                {
                    using (SPSite miSitio = new SPSite(this.Properties["sitioJobUrl"].ToString()))
                    {
                        using (SPWeb miWeb = miSitio.OpenWeb())
                        {
                            // El procesamiento de nuestro job 
                            // El procesamiento de nuestro job 
                            // El procesamiento de nuestro job 
                            // El procesamiento de nuestro job 
                        }
                    }
                });
            }
        }

        // Administración del JOB

    }

El resultado



Algunos puntos a tener en cuenta:
  • El usuario que activa la feature debe tener permisos para crear jobs
  • El usuario que desactiva la feature debe tener permisos para borrar jobs
  • Es posible que se requiera reiniciar el Time job luego de hacer un despliegue para que el job tome la siguiente versión del código.



Bibliográfica:

martes, 28 de julio de 2015

¿Cómo modificar el display name de una columna en una lista de SharePoint con C#?

        public string alterarDisplayNameCampo(SPWeb sitio, string listaP, string columnaIN, string nuevoDN)
        {
            SPList lista = sitio.Lists[listaP];
            SPField columna = lista.Fields.GetFieldByInternalName(columnaIN);
            if (columna.Title == nuevoDN)
                return "<span style='color: blue'>Columna " + columnaIN + " no modificada.</span><br>";
            else
            {
                columna.Title = nuevoDN;
                columna.Update();
                return "<span style='color: green'>Columna " + columnaIN + " modificada.</span><br>";
            }
        }

lunes, 27 de julio de 2015

Programando la Ribbon en SharePoint 2010.

Introducción

Es bastante común que se necesiten agregar botones a la barra de herramientas de SharePoint 2010 (RIBBON). Muchas veces esto se resuelve en forma declarativa o con SharePoint Designer, pero otras veces necesitamos ir un poco más allá, por ejemplo, ejecutar código c# luego de presionar un botón o lograr que el botón este asociado a una lista específica y no a un tipo de lista.

En este artículo veremos un ejemplo de cómo lograr este tipo de programación con Visual Studio. Que lo disfruten!

 

El objetivo

Nuestro objetivo es agregar tres botones a la página de edición de una lista en particular. Esos tres botones estarán agrupados bajo un mismo título tal como muestra la imagen 1:

image

Imagen 1 – Botones agregados a la Ribbon.

 

Al presionar el botón, se abre una página ASPX en C# que nos permite trabajar con la API de SharePoint tal como muestra la imagen 2:

image

Imagen 2 – Página de aplicación llamada desde el botón de la Ribbon.

¿Cómo agregar los botones?

Los botones suelen agregarse en forma declarativa, pero en este caso lo haremos mediante código c# para poder agregarlos en una lista en particular, y no en un tipo de lista. En este caso lo haremos a través de un evento de activación de feature. Esto puede verse en esta parte del código:

 

public override void FeatureActivated(SPFeatureReceiverProperties properties)

{

using (SPWeb mi_web = (properties.Feature.Parent as SPSite).OpenWeb())

{

// Agrego botones personalizados para la edición del Proyecto

mi_web.AllowUnsafeUpdates = true;

SPList lista = mi_web.GetList("/Lists/Proyectos");

var action = lista.UserCustomActions.Add();

action.Location = "CommandUI.Ribbon.EditForm";

action.Sequence = 10;

action.Title = "SURPOINT";

action.CommandUIExtension = @"

 

Observar especialmente como agregar los botones en la página de edición de la lista: "CommandUI.Ribbon.EditForm".

Dos puntos adicionales:

· Los botones estarán dentro de un grupo

· Al presionar el botón ejecutamos código JavaScript para llamar a una página ASPX.

Esa página ASPX se carga en una ventana modal. Además se controla el resultado de esa ventana para decidir si se cierra o no la pantalla de Edición del ítem. Esto puede verse en esta línea de código:

 

CommandAction=""javascript:

elID = _spGetQueryParam('id');

var dlg=SP.UI.ModalDialog.showModalDialog({url: '{SiteUrl}/_Layouts/EMP_Procesos/VincularProyecto.aspx?ProjectId='+elID, dialogReturnValueCallback:function(res, val) { if (res == 1) window.frameElement.commonModalDialogClose(1, 0); } });

"" />

 

El siguiente es el código completo para agregar los botones

 

public override void FeatureActivated(SPFeatureReceiverProperties properties)

{

using (SPWeb mi_web = (properties.Feature.Parent as SPSite).OpenWeb())

{

// Agrego botones personalizados para la edición del Proyecto

mi_web.AllowUnsafeUpdates = true;

SPList lista = mi_web.GetList("/Lists/Proyectos");

var action = lista.UserCustomActions.Add();

action.Location = "CommandUI.Ribbon.EditForm";

action.Sequence = 10;

action.Title = "SURPOINT";

action.CommandUIExtension = @"

<CommandUIExtension>

<CommandUIDefinitions>

<CommandUIDefinition Location=""Ribbon.Templates._children"">

<GroupTemplate Id=""Ribbon.Templates.CustomTemplate"">

<Layout Title=""NewGroupInExistingTabOneLarge"" LayoutTitle=""NewGroupInExistingTabOneLarge"">

<Section Alignment=""Top"" Type=""OneRow"">

<Row>

<ControlRef DisplayMode=""Large"" TemplateAlias=""Button1"" />

<ControlRef DisplayMode=""Large"" TemplateAlias=""Button2"" />

<ControlRef DisplayMode=""Large"" TemplateAlias=""Button3"" />

</Row>

</Section>

</Layout>

</GroupTemplate>

</CommandUIDefinition>

<CommandUIDefinition Location=""Ribbon.ListForm.Edit.Scaling._children"">

<MaxSize

Id=""Ribbon.ListForm.Edit.MaxSize""

Sequence=""15""

GroupId=""Ribbon.ListForm.Edit.Groups.CustomGroup""

Size=""NewGroupInExistingTabOneLarge"" />

</CommandUIDefinition>

<CommandUIDefinition Location=""Ribbon.ListForm.Edit.Groups._children"">

<Group Id=""Ribbon.ListForm.Edit.Groups.CustomGroup""

Sequence=""100"" Title=""SURPOINT""

Description=""SURPOINT""

Template=""Ribbon.Templates.CustomTemplate"">

<Controls Id=""Ribbon.ListForm.Edit.Groups.CustomGroup.Controls"">

<Button

Id=""Ribbon.ListForm.Edit.Groups.CustomGroup.Controls.Button""

Alt=""Vincular a sitio de proyecto""

Sequence=""1""

Image32by32=""/_layouts/images/newweb32.png""

Image16by16=""/_layouts/images/SharePointFoundation10.png""

Command=""vincularSitio""

LabelText=""Sitio de proyecto""

TemplateAlias=""Button1""

CommandType=""General""/>

<Button

Id=""Ribbon.ListForm.Edit.Groups.CustomGroup.Controls.Button2""

Alt=""Agregar a informe de portfolio""

Sequence=""2""

Image32by32=""/_layouts/images/uploaddoc.png""

Image16by16=""/_layouts/images/SharePointFoundation10.png""

Command=""agregarInforme""

LabelText=""Agregar a portfolio""

TemplateAlias=""Button2""

CommandType=""General""/>

<Button

Id=""Ribbon.ListForm.Edit.Groups.CustomGroup.Controls.Button3""

Alt=""Quitar de informe de portfolio""

Sequence=""3""

Image32by32=""/_layouts/images/mewa_backb.gif""

Image16by16=""/_layouts/images/SharePointFoundation10.png""

Command=""quitarInforme""

LabelText=""Quitar de portfolio""

TemplateAlias=""Button3""

CommandType=""General""/>

</Controls>

</Group>

</CommandUIDefinition>

</CommandUIDefinitions>

<CommandUIHandlers>

<CommandUIHandler

Command=""vincularSitio""

CommandAction=""javascript:

elID = _spGetQueryParam('id');

var dlg=SP.UI.ModalDialog.showModalDialog({url: '{SiteUrl}/_Layouts/EMP_Procesos/VincularProyecto.aspx?ProjectId='+elID, dialogReturnValueCallback:function(res, val) { if (res == 1) window.frameElement.commonModalDialogClose(1, 0); } });

"" />

<CommandUIHandler

Command=""agregarInforme""

CommandAction=""javascript:

elID = _spGetQueryParam('id');

var dlg=SP.UI.ModalDialog.showModalDialog({url: '{SiteUrl}/_Layouts/EMP_Procesos/proyectoInforme.aspx?Accion=agregar&amp;ProjectId='+elID, dialogReturnValueCallback:function(res, val) { if (res == 1) window.frameElement.commonModalDialogClose(1, 0); } });

"" />

<CommandUIHandler

Command=""quitarInforme""

CommandAction=""javascript:

elID = _spGetQueryParam('id');

var dlg=SP.UI.ModalDialog.showModalDialog({url: '{SiteUrl}/_Layouts/EMP_Procesos/proyectoInforme.aspx?Accion=quitar&amp;ProjectId='+elID, dialogReturnValueCallback:function(res, val) { if (res == 1) window.frameElement.commonModalDialogClose(1, 0); } });

"" />

</CommandUIHandlers>

</CommandUIExtension>

";

action.Update();

mi_web.AllowUnsafeUpdates = false;

}

}

Las páginas de aplicación

Las páginas que son llamadas desde la RIBBON son páginas de aplicación típicas de SharePoint. Ejecutan el código c# que necesitemos. Sólo tenemos que tener en cuenta que deben encargarse de cerrar la pantalla modal.

A modo ilustrativo, muestro parte del código de carga de la página:

 

protected void Page_Load(object sender, EventArgs e)

{

if (!IsPostBack)

{

// Obtener el ID del proyecto recibido como parámetro

string proyecto = "";

proyecto = Page.Request.QueryString["ProjectId"];

// Datos del actual sitio elegido

string idSitio = "";

using (SPSite sitio = new SPSite(SPContext.Current.Site.Url))

(…)

 

Y lo más importante la parte final del evento OnClick del botón de nuestra página:

 

protected void vincularSitio(object sender, EventArgs e)

{

(..(

this.Page.Response.Clear();

this.Page.Response.Write("<script type='text/javascript'>window.frameElement.commonModalDialogClose(1, 1);</script>");

this.Page.Response.End();

}

 

Utilizamos commonModalDialogClose para cerrar la ventana

 

Conclusión

En este breve artículo vimos que no es tan complejo agregar componentes avanzados en la RIBBON de SharePoint. La posibilidad de ejecutar código c# de servidor, nos abre un abanico de posibilidades para extender nuestros sitios.

Y lo que es más importante, se hace de una forma que resulta bastante amigable para el usuario, ya que la RIBBON es una barra que se ha acostumbrado a utilizar.

Hasta la próxima!

 

Juan Pablo Pussacq Laborde

SharePoint MVP

Blog: http://surpoint.blogspot.com/

 

Publicado originalmente para revista COMPARTIMOSS

Diseño Gráfico en SharePoint Foundation 2010 Parte 3.

Introducción

En artículos anteriores hicimos una introducción acerca de cómo incorporar en SharePoint 2010 los estilos creados por un diseñador gráfico. Nos basamos en algunas hipótesis como el uso de SharePoint Foundation, páginas maestras, hojas de estilo y modelo de objetos de cliente para JavaScript. Se recomienda leer la parte 1 y la parte 2 antes de continuar. También les recomiendo leer los ejemplos de CAML con Client Object Model, porque los utilizo para obtener datos de listas.

En esta tercera etapa, vamos a trabajar con dos casos puntuales:

· Botonera gráfica en reemplazo del Inicio Rápido

· Tablero de control con indicadores gráficos

Qué lo disfruten!

 

Botonera gráfica en reemplazo del Inicio Rápido

Lo primero que vamos a mostrar es cómo agregar una botonera con imágenes que reemplace al tradicional Inicio Rápido. Esta botonera se verá en todas las páginas. Para ello modificamos la página maestra y agregamos algunos estilos. El resultado final se ve en esta imagen:

image

Imagen 1 – Botonera gráfica en reemplazo de Inicio Rápido

 

El siguiente código muestra el agregado de la botonera en la página maestra

<div id="botonera_izquierda">

<div id="menu">

<ul id="nav">

<li><a href="/"><img src="/internal/nav_inicio_static.jpg" width="90" height="90"></a></li>

<li><a href="/Lists/Noticias"><img src="/internal/nav_noticias_static.jpg" width="90" height="90"></a></li>

<li><a href="/Lists/Personal"><img src="/internal/nav_nuestras_static.jpg" width="90" height="92"></a></li>

<li><a href="/Polticas"><img src="/internal/nav_politicas_static.jpg" width="90" height="91"></a></li>

<li><a href="/Formularios"><img src="/internal/nav_formularios_static.jpg" width="90" height="91"></a></li>

<li><a href="/Lists/Busquedas"><img src="/internal/nav_busquedas_static.jpg" width="90" height="91"></a></li>

<li><a href="/Lists/Menu"><img src="/internal/nav_menu_static.jpg" width="90" height="91"></a></li>

<li><a href="/Revistas/"><img src="/internal/nav_revista_static.jpg" width="90" height="91"></a></li>

<li><a href="/"><img src="/internal/nav_remises_static.jpg" width="90" height="91"></a></li>

<li><a href="/Capacitaciones"><img src="/internal/nav_capacitaciones_static.jpg" width="90" height="92"></a></li>

</ul>

</div>

</div>

 

El código debe ser agregado justo antes de esta línea:

<div id="s4-leftpanel-content">

 

Por otro lado, agregamos los siguientes estilos en nuestra hoja de estilos:

#s4-leftpanel { width: 95px !important;}

#s4-leftpanel-content { display: none; }

.s4-ca { margin-left: 95px; }

 

Un detalle más, queremos que los botones cambien a otra imagen en el momento en que pasamos el mouse por sobre ellos (hover), tal como se ve en la siguiente imagen:

image

Imagen 2 – Efecto Hover en la botonera

 

Con ese fin agregamos en la página maestra, el siguiente código jQuery:

<script type="text/javascript">

$('#nav a img').hover(

function() {

$(this).attr('src', $(this).attr('src').replace('static', 'over') );

},

function() {

$(this).attr('src', $(this).attr('src').replace('over', 'static') );

}

);

</script>

Tablero de control con indicadores gráficos

Cómo segundo ejemplo del día, vamos a agregar unos indicadores gráficos que se vean como los de la siguiente imagen:

image

Imagen 3 – Ejemplo de Tablero de Control

Estos indicadores serán generados a partir de información en listas usando CAML. En este caso usaremos una página de aplicación en C# con código de servidor por un tema de rendimiento, pero también podría ser JavaScript con Cliente Object Model.

Dentro de nuestra página ASP.Net llamaremos a una hoja de estilos además de crear el DIV “contenido”:

<asp:Content ID="Main" runat="server" contentplaceholderid="PlaceHolderMain" >

<div runat="server" ID="contenido" />

<link href="agea_telefonia_dashboard.css" type="text/css" rel="stylesheet" id="stylesheet" />

</asp:Content>

<asp:Content ID="PageTitle" runat="server" contentplaceholderid="PlaceHolderPageTitle" >

Panel de Control

</asp:Content>

<asp:Content ID="PageTitleInTitleArea" runat="server" contentplaceholderid="PlaceHolderPageTitleInTitleArea" >

Panel de Control

</asp:Content>

 

Leeremos la información vía CAML. No voy a mostrar en el código la lectura de información porque está muy relacionada con la lógica del negocio y no es más que lecturas típicas en CAML. Lo que si mostraré es cómo incorporar esos valores dentro de dos DIVs pensados para estructurar la información:

· DIV dash01 para el valor del indicador 1

· DIV dash01_detalle para el detalle del indicador 1

· Y así sucesivamente…

 

contenido.InnerHtml = "<div class='semaforos'>";

contenido.InnerHtml += "<div id='dash01' class='" + luz_indicador_1 + " dash'><p class='titulo'>CALENDARIO</p><p class='valor'>" + x_equipos_sin_linea + "</p><p class='leyenda'>Tiempos del Proyecto</p></div>";

contenido.InnerHtml += "<div id='dash02' class='" +

Continuar el armado de DIVs para el resto de los indicadores.

contenido.InnerHtml += "</div>";

contenido.InnerHtml += "<div id='dash01_detalle' class='detalle'>" + detalle_indicador_1 + "</div>";

contenido.InnerHtml += "<div id='dash02_detalle' class='detalle'>" +

Continuar el armado de DIVs de detalle para el resto de los indicadores.

 

Agregaremos además algo de código jQuery para que al presionar sobre un indicador, nos muestre la información de detalle. Para ello llamamos a los scripts correspondientes de ASPX:

Page.ClientScript.RegisterClientScriptInclude("dash_jquery", ResolveUrl("./jquery-1.8.2.min.js"));

Page.ClientScript.RegisterClientScriptInclude("dash_script", ResolveUrl("./dashboard.js"));

Page.ClientScript.RegisterStartupScript(this.GetType(), "dash_script", "asignar_botones();", true);

 

Y este es nuestro código jQuery “dashboard.js”:

function asignar_botones () {

$( document ).ready(function() {

$(".dash").click(function() {

var panel = "#" + $(this).attr('id') + "_detalle";

$(".detalle").css("display","none");

$(panel).slideDown( "slow" ).css("overflow","visible");

$(".dash").removeClass("dash_selected");

$(this).addClass("dash_selected");

});

});

 

Por último nos queda incorporar nuestra hoja de estilo, cuidando que el diseño sea responsive y se adapte al ancho de la pantalla, como se ve en esta imagen (en una pantalla más angosta que la anterior):

image

Imagen 4 – Ejemplo de Tablero de Control con un ancho de pantalla más angosto

 

.dash { border: solid white 2px; }

.dash_selected { border: solid #333333 2px; }

.semaforos div {

float: left;

width: 22%;

margin: 3px;

padding: 15px 15px 10px 15px ;

}

.semaforos {

font-family: tahoma;

text-align: center;

cursor: pointer;

}

.verde { background-color: #8CC152; }

.rojo {background-color: #E9573F; }

.amarillo { background-color: #F6BB42;}

.gris{ background-color: #C8C8C; }

.valor {

color: #fff;

font-weight: 500;

font-size: 36px;

margin-top: 20px;

margin-bottom: 10px;

}

.titulo {

color: #fff;

font-size: 17px;

font-weight: 500;

}

.leyenda {

border-top: solid white 5px;

padding-top: 12px;

color: #fff;

font-size: 12px;

height: 65px;

}

.verde .leyenda { border-top-color: #76AD3E;}

.rojo .leyenda { border-top-color: #D1462C;}

.amarillo .leyenda { border-top-color: #E0A52C; }

.gris .leyenda { border-top-color: #737373; }

.detalle {

border-left: solid #E6E9ED 10px;

width: 95%;

height: 200px;

margin: 5px;

padding: 5px;

font-size: 11px;

font-family: tahoma;

clear: both;

display:none;

}

.detalle span {

font-size: 11px;

font-family: tahoma;

}

.detalle a {

padding: 5px;

font-size: 11px;

}

Conclusión

Esta fue la tercera entrega de esta colección de artículos dedicada a demostrar cómo se puede trabajar en SharePoint junto a un diseñador gráfico profesional. En mi opinión, esto es muy valorado por los clientes.

Les presento otra imagen del resultado final, ya vieron una en el artículo anterior. Espero como siempre que haya sido útil y cualquier duda me consultan. Saludos!

image

Imagen 5 – Diseño Gráfico aplicado a la página de inicio de SharePoint (caso 2)

 

Juan Pablo Pussacq Laborde

SharePoint MVP

Blog: http://surpoint.blogspot.com/

Facebook: http://facebook.com/surpointblog/

Twitter: http://twitter.com/jpussacq/

 

Publicado originalmente para revista COMPARTIMOSS

domingo, 26 de julio de 2015

¿Cómo verificar si un usuario tiene permisos de edición en una lista de SharePoint con C#?

bool tienePermisosChecklist = false;
SPSecurity.RunWithElevatedPrivileges(delegate()
{
  using (SPWeb mi_web = SPContext.Current.Web)
  {
    SPList listaChecklist = mi_web.Lists.TryGetList("Checklist");
    if (listaChecklist != null)
      tienePermisosChecklist = listaChecklist.DoesUserHavePermissions(mi_web.CurrentUser, SPBasePermissions.EditListItems);
  }
});

viernes, 24 de julio de 2015

Mi escritorio


miércoles, 22 de julio de 2015

¿Cómo editar una vista de una webpart en una página en SharePoint con C#?

Supongamos que tenemos que modificar el filtro de una vista en la página de inicio de nuestro sitio.

Esta es nuestra webpart:



Y este el filtro:



El enfoque a utilizar:
  • Encontrar la página de inicio
  • Buscar la webpart por título
  • Instanciar su vista
  • Cambiar el QUERY
Para ello utilizamos este código:


                                    SPFile homeFile = oWebsite.GetFile("default.aspx");
                                    using (SPLimitedWebPartManager mgr = homeFile.GetLimitedWebPartManager(PersonalizationScope.Shared))
                                    {
                                        SPLimitedWebPartCollection webparts = mgr.WebParts;
                                        foreach (System.Web.UI.WebControls.WebParts.WebPart wp in webparts)
                                        {
                                            if (wp.Title == "Issues y riesgos (demorados)")
                                            {
                                                XsltListViewWebPart lv = (XsltListViewWebPart)wp;
                                                var viewId = new Guid(lv.ViewGuid);
                                                SPView v = listaRiesgos.Views[viewId];
                                                v.Query = "<OrderBy><FieldRef Name=\"Title\" /></OrderBy><Where><Eq><FieldRef Name=\"Estado_Cumplimiento_Riesgo\" /><Value Type=\"Text\">Plan demorado</Value></Eq></Where>";
                                                v.Update();
                                                contenido.InnerHtml += "<span style='color: green'>" + wp.Title + " actualizada!</span><br>";
                                            }
                                        }
                                    }

Y vemos el resultado:


¿Cómo ocultar una columna en un tipo de contenido en SharePoint con C#?

        // Ocultar columna en Tipo de Contenido
        public string ocultarColumnaTipoContenido(SPSite sitio, string columna, string tipoContenido)
        {
            SPContentType tc = sitio.RootWeb.ContentTypes[tipoContenido];
            SPFieldLink colLink = tc.FieldLinks[columna];
            if (!colLink.Hidden)
            {
                colLink.Hidden = true;
                tc.Update(true);
                return "<span style='color: green'>Columna " + columna + " ocultada en " + tipoContenido + ".</span><br>";
            }
            else
                return "<span style='color: blue'>Columna " + columna + " ya ya estaba oculta en " + tipoContenido + ".</span><br>";
        }


martes, 21 de julio de 2015

Participando en Sharepoint.Stackexchange.com

Hace un tiempo que empecé a participar en este foro. Es muy interesante. Un premio para esta semana :-)




viernes, 17 de julio de 2015

¿Cómo ordenar campos en un tipo de contenido en SharePoint con C#?

En este breve artículo mostramos cómo reordenar los campos de un tipo de contenido. Tomamos cómo hipótesis que no necesariamente tenemos que especificar todos los campos, sino sólo los que queremos ordenar. 

Aquí el código:

SPContentType ctRiesgos = oSiteCollection.RootWeb.ContentTypes["Issues y riesgos"];
SPFieldLinkCollection campos = ctRiesgos.FieldLinks;
if (campos[2].Name != "Opciones_Probabilidad_Riesgo")
{
  campos.Reorder(new[] { "Title", "Tipo_Riesgo", "Opciones_Probabilidad_Riesgo", "Opciones_Impacto_Riesgo", "Impacto_Riesgo" });
  contenido.InnerHtml += "<span style='color: green'>Campos de riegos ordenados.</span><br>";
  ctRiesgos.Update(true);
}
else
  contenido.InnerHtml += "<span style='color: blue'>Campos de riegos ya estaban ordenados.</span><br>";

Acá el resultado:



jueves, 4 de junio de 2015

Project 2013 - Columna indicador de estado

La columna indicador de estado es una columna estándar que no requiere una fórmula como un campo personalizado. Según la ayuda de Microsoft, puede tomar estos valores:
  • Si la tarea está completada, el campo Indicador de estado contendrá una marca de verificación.
  • Si la tarea se va a realizar en el futuro, el campo Indicador de estado estará en blanco.
  • Si la tarea va según lo programado, el campo Indicador de estado contiene un reloj con una marca de verificación.
  • Si la tarea está retrasada, el campo Indicador de estado contiene un reloj con un signo de exclamación.

Y visualmente se ve como en esta foto:




domingo, 24 de mayo de 2015

¿Cómo habilitar versiones en una lista en SharePoint con C#?

SPList listaKPIs = oWebsite.Lists.TryGetList("KPIs");
if (listaKPIs != null)
{
  if (!listaKPIs.EnableVersioning)
  {
    listaKPIs.EnableVersioning = true;
    listaKPIs.Update();
  }
}

lunes, 18 de mayo de 2015

¿Cómo ocultar un campo en un tipo de contenido en SharePoint con C#?

if (listaInformes.ContentTypes["Elemento"].FieldLinks["Porcentual_Portfolio"].Hidden)
  contenido.InnerHtml += "<span style='color: blue'>Informes ya tiene porcentual OCULTO</span><br>";
else
{
  listaInformes.ContentTypes["Elemento"].FieldLinks["Porcentual_Portfolio"].Hidden = true;
  listaInformes.ContentTypes["Elemento"].Update();
  contenido.InnerHtml += "<span style='color: green'>Porcentual OCULTO en Informes</span><br>";
}

lunes, 27 de abril de 2015

¿Cómo crear un tipo de contenido en SharePoint con C#?

public string nuevoTipoContenido(SPSite sitio, string nombre)
{
  SPContentType viejoTC = sitio.RootWeb.ContentTypes[nombre];
  if (viejoTC == null)
  {
    SPContentType nuevoTC = new SPContentType(sitio.RootWeb.ContentTypes["Elemento"], sitio.RootWeb.ContentTypes, nombre);
    sitio.RootWeb.ContentTypes.Add(nuevoTC);
    nuevoTC.Group = "PMO Procesos";
    nuevoTC.Update();
    return "<span style='color: green'>Tipo de contenido " + nombre + " creado.</span><br>";
  }
  else
  {
    return "<span style='color: blue'>Tipo de contenido " + nombre + " ya existente.</span><br>";
  }
}

¿Cómo agregar nodos en el Inicio Rápido / Top Link Bar en SharePoint con C#?

En este breve post mostramos como agregar programáticamente nodos en la barra de vínculos superior y/o en el inicio rápido de SharePoint:

contenido.InnerHtml += agregarNodoNavegacion(oWebsite, oWebsite.Navigation.QuickLaunch, "Charter", "Macroplan", "javascript: window.location.href((_spPageContextInfo.webServerRelativeUrl+'/lists/macroplan').replace('//','/'));", true);
                                    
contenido.InnerHtml += agregarNodoNavegacion(oWebsite, oWebsite.Navigation.TopNavigationBar, "Charter", "Macroplan", "javascript: window.location.href((_spPageContextInfo.webServerRelativeUrl+'/lists/macroplan').replace('//','/'));", true);


public string agregarNodoNavegacion(SPWeb web, SPNavigationNodeCollection menu, string previo, string nuevoNombre, string nuevoURL, bool  nuevoExterno)
{
            
            bool existeNuevo = false;
            int indicePrevio = 0;
            int indice = 0;
            
            foreach (SPNavigationNode itemMenu in menu)
            {
                if (itemMenu.Title == nuevoNombre) existeNuevo = true;
                if (itemMenu.Title == previo) indicePrevio = indice;
                indice++;
            }
            
            if (!existeNuevo && indicePrevio != 0)
            {
                SPNavigationNode nuevoNavigation = new SPNavigationNode(nuevoNombre, nuevoURL, nuevoExterno);
                menu.Add(nuevoNavigation, menu[indicePrevio]);
                return "<span style='color: green'>" + web.Title + ": " + nuevoNombre + " agregado en Navegación</span><br>";
            }
            else
                return "<span style='color: blue'>" + web.Title + ": Ya existe " + nuevoNombre + " en Navegación o no existe " + previo + "</span><br>";
}

viernes, 24 de abril de 2015

¿Cómo cambiar el display name de una columna de lista en SharePoint con C#?

SPList listaProyectos = oSiteCollection.RootWeb.Lists["Proyectos"];
SPField suplentes = listaProyectos.Fields.GetFieldByInternalName("Suplentes");
if (suplentes.Title != "Suplentes")
  {
  contenido.InnerHtml += "No se renombra.";
}
else
{
  suplentes.Title = "Otros referentes";
  suplentes.Update();
  contenido.InnerHtml += "Renombrada";
}

¿Cómo crear una columna de sitio en SharePoint con C#?

if (!oSiteCollection.RootWeb.Fields.ContainsField("Brief"))
{
  oSiteCollection.RootWeb.Fields.Add("Brief", SPFieldType.Note, false);
  SPFieldMultiLineText brief = (SPFieldMultiLineText)oSiteCollection.RootWeb.Fields.GetFieldByInternalName("Brief");
  brief.NumberOfLines = 12;
  brief.RichText = false;
  brief.Group = "PMO Procesos";
  brief.Update();
  contenido.InnerHtml += "OK";
}
else
{
  contenido.InnerHtml += "Ya existente";
}


jueves, 23 de abril de 2015

¿Cómo hacer que un campo de tipo persona acepte valores múltiples en SharePoint con C#?

// Hacer multivaluado un campo persona
public string alterarCampoPersona(SPSite sitio, string listaP, string columnaP)
{
            SPList lista = sitio.RootWeb.Lists[listaP];
            SPFieldUser columna = (SPFieldUser)lista.Fields[columnaP];

            if (columna.AllowMultipleValues)
                return "Ya es";
            else
            {
                columna.AllowMultipleValues = true;
                columna.Update();
                return "OK";
            }
}

¿Cómo agregar una validación de lista en SharePoint con C#?

El código

SPList listaManual = oSiteCollection.RootWeb.Lists["Manual"];
listaManual.ValidationFormula = "=OR(AND(Valor3=\"\";Color=\"Rojo\");AND(Valor3<>\"\";Color<>\"Rojo\"))";

listaManual.ValidationMessage = "Valor3 es obligatorio para Verde y Amarillo y debe ser nulo para Rojo.";
listaManual.Update();


El resultado




miércoles, 22 de abril de 2015

¿Cómo crear una lista en SharePoint con C#?

En este post veremos cómo:
  • Crear una lista
  • Crearle un campo de texto, un campo URL y un campo calculado
  • Ajustar el campo título
  • Ajustas la vista predeterminada (Requiere post anterior)
  • Cargar los primeros ítems
Qué les sea útil!

try
{
    SPSite oSiteCollection = SPContext.Current.Site;

    // Nueva lista de Configuración
    SPList listaProyectoManual = oSiteCollection.RootWeb.Lists.TryGetList("Manual");
    if (listaProyectoManual == null)
    {
        // Nueva lista
        SPListCollection listas = oSiteCollection.RootWeb.Lists;
        listas.Add("Manual", "Conversión de porcentaje a número en proyectos manuales", SPListTemplateType.GenericList);
        contenido.InnerHtml += "<span style='color: green'>Lista de configuración de Proyecto Manuales creada</span><br>";

        // Campos
        SPList listaManual = oSiteCollection.RootWeb.Lists["Manual"];
        listaManual.Fields.Add("Color", SPFieldType.Choice, true);
        SPFieldChoice color = listaManual.Fields["Color"] as SPFieldChoice;
        color.Choices.Add("Verde");
        color.Choices.Add("Amarillo");
        color.Choices.Add("Rojo");
        color.Update();
        listaManual.Fields.Add("Valor1", SPFieldType.Number, true);
        listaManual.Fields.Add("Valor2", SPFieldType.Number, true);
        listaManual.Fields.Add("Valor3", SPFieldType.Number, false);
        contenido.InnerHtml += "<span style='color: green'>Columnas agregadas</span><br>";

        // Campo calculado
        string formulaAmigable = listaManual.Fields.Add("Formula", SPFieldType.Calculated, false);
        SPFieldCalculated formulaAmigableCF = listaManual.Fields["Formula"] as SPFieldCalculated;
        formulaAmigableCF.Formula = "=IF(Color<>\"Rojo\";\"(\"&Color&\") = \"&Valor1&\" + \"&Valor2&\" x (P - \"&Valor3&\")\";\"(\"&Color&\") = \"&Valor1&\" + P / \"&Valor2)";
        formulaAmigableCF.OutputType = SPFieldType.Text;
        formulaAmigableCF.Update();
        contenido.InnerHtml += "<span style='color: green'>Campo calculado creado</span><br>";
                            
        // Ajustes al título
        listaManual.Fields["Título"].Required = false;
        listaManual.Fields["Título"].Hidden = true;
        listaManual.Fields["Título"].Update();
        contenido.InnerHtml += "<span style='color: green'>Título ajustado</span><br>";

        // Vista default
        SPView vistaManual = listaManual.DefaultView;
        vistaManual.ViewFields.DeleteAll();
        vistaManual.ViewFields.Add("Edit");
        vistaManual.ViewFields.Add("Formula");
        vistaManual.Query = "<OrderBy><FieldRef Name=\"ID\"/></OrderBy>";
        vistaManual.Update();
        SetToolbarType(vistaManual, "None");
        contenido.InnerHtml += "<span style='color: green'>Vista ajustada</span><br>";

        // Crear ítems
        SPListItem item1 = listaManual.Items.Add();
        item1["Color"] = "Verde";
        item1["Valor1"] = 2;
        item1["Valor2"] = 10;
        item1["Valor3"] = 0.9;
        item1.Update();
        SPListItem item2 = listaManual.Items.Add();
        item2["Color"] = "Amarillo";
        item2["Valor1"] = 1.5;
        item2["Valor2"] = 2.5;
        item2["Valor3"] = 0.7;
        item2.Update();
        SPListItem item3 = listaManual.Items.Add();
        item3["Color"] = "Rojo";
        item3["Valor1"] = 1;
        item3["Valor2"] = 1.4;
        item3.Update();
        contenido.InnerHtml += "<span style='color: green'>Fórmula creada</span><br>";

    }
    else
    {
        contenido.InnerHtml += "<span style='color: blue'>Ya existe la lista de configuración de Proyecto Manuales</span><br>";
    }
}
catch (Exception error)
{
       contenido.InnerHtml += "<span style='color: red'>Error: " + error + "</span><br>";
}