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 :-)

miércoles, 19 de marzo de 2014

Problemas con agrupamientos en SharePoint 2010

Me he encontrado con un problema al configurar las opciones de agrupamiento en una vista de SharePoint 2010. No se muestran los elementos y aparece la leyenda "Cargando" tal como muestro en esta imagen:



La solución en mi caso ha pasado por descargar e instalar el siguiente hotfix: http://support.microsoft.com/kb/2553117/en-us

Nota: aparentemente el problema se da sólo en instalaciones con paquetes de idioma.

Luego de aplicar el hotfix, este es el resultado:



Espero les haya resultado útil, hasta la próxima!

martes, 4 de marzo de 2014

¿Cómo mover un sitio sin perder la historia?

Introducción

El objetivo de esta prueba es identificar y validar un método para mover un sitio dentro de la estructura de sitios sin perder datos de la historia.

Escenario:

  • Tenemos una colección de sitios de nivel superior llamada “PADRE”
  • Tenemos un sitio hijo llamado “HIJO” y un sitio hijo de este hijo llamado “NIETO”.

image

El objetivo es subir un nivel en la estructura a NIETO sin que se pierda la historia:

image

No perder la historia incluye:

  • Historial de modificación de documentos (fechas y usuarios)
  • Flujos de trabajo y su historial de aprobaciones
  • Permisos (asumiendo que se heredan, aunque pueden existir permisos exclusivos)


Ambiente

Para realizar esta prueba, creamos un ambiente con las siguientes características:

  • Windows Server 2008 R2
  • SQL Server 2008 R2
  • SharePoint Server 2010 Enterprise

 

Paso 1 - Creación de sitios

A partir de una colección de sitios existentes, creamos primero el sitio HIJO, heredando permisos del padre:

image

Luego creamos un sitio NIETO, heredando los permisos de HIJO. Obtenemos una estructura como la siguiente:

image

 

Paso 2 - Creación de Información

En este segundo paso, vamos a crear la historia del sitio NIETO:

  • Listas
  • Flujo de trabajo
  • Carga de datos y ejecución del flujo de trabajo

 

Creación de Listas

Creamos una biblioteca de documentos y una lista de tareas.

image

A estas dos listas, les activamos el control de versiones:

image

image

 

Creación de Flujos de Trabajo

Creamos un flujo de trabajo para la biblioteca de documentos. Es un flujo sencillo que tienes los siguientes pasos al momento de subir un documento:

  1. Asigna una tarea a un usuario
  2. Registra un evento en el historial

image

Definimos las opciones del flujo de trabajo para que se dispare automáticamente al momento de subir un documento:

image

 

Carga de datos y ejecución del flujo de trabajo

Trabajamos primero con el Documento 1:

  • Subimos el documento
  • Completamos la tarea asignada por el flujo de trabajo
  • Más tarde hacemos una modificación al documento

La historia del flujo de trabajo muestra:

image

La historia del documento muestra la creación y una modificación posterior:

image

La historia de la tarea muestra la creación y la modificación cuando se ejecutó el flujo de trabajo:

image

 

Paso 3 - Ajustes de la seguridad

Ajustamos la seguridad utilizando el siguiente criterio:

  • El sitio hereda permisos
  • La lista de documentos hereda permisos
  • La lista de tareas posee permisos exclusivos, según se muestra en la siguiente imagen:

image

 

Movimiento del sitio

Introducción

Para realizar el movimiento del sitio, vamos a trabajar con la opción Contenido y Estructura dentro de la Configuración del Sitio:

image

Dentro de esta opción, utilizaremos el comando Mover.

 

Pasos para mover el sitio

Una vez dentro de la estructura, veremos reflejada la actual jerarquía:

image

Abrimos la lista desplegable de NIETO y elegimos la opción MOVER:

image

Aparecerá un cuadro de diálogo para elegir el sitio destino, elegimos PADRE:

image

Presionamos aceptar y esperamos... Luego veremos reflejada la nueva estructura:

image

 

Verificaciones

Movimiento dentro de la estructura

Lo primero que se observa es que NIETO ya no es hijo de HIJO:

image

Seguridad

Se observa que se mantuvieron las configuraciones de seguridad:

  • Permisos heredados en el sitio y la lista de documentos
  • Permisos exclusivos en la lista de tareas

Historial de Versiones

Al verificar el historial de versiones del Documento 1 se observan los mismos valores que antes:

image

Lo mismo sucede den el historial de versiones de la tarea:

image

Historial de Versiones en el Flujo de Trabajo

En la siguiente pantalla se ven los detalles de la historia del flujo de trabajo con los mismos usuarios y fechas:

image

Movimiento del Flujo de Trabajo

Abrimos SharePoint Designer y vemos que el flujo de trabajo existe:

image

El segundo paso es subir un segundo documento al sitio, para verificar que el flujo de trabajo funcione correctamente. Para ello luego de subirlo, completamos la tarea asignada. El resultado es el siguiente:

image

Por último, volvemos a abrir SharePoint Designer y modificamos el flujo de trabajo agregando un tercer paso. Publicamos y vemos que todo funciona sin problemas:

image

Subimos un tercer documento y verificamos que el flujo de trabajo se ejecuta sin problemas:

image

 

Conclusión

En esta prueba se observa que SharePoint 2010 permite mover sitios dentro de la estructura manteniendo la historia de la información. Es una prueba de laboratorio sencilla sin estructuras complejas.

Antes de avanzar con este método en un ambiente productivo, es conveniente revisar las particularidades de cada caso, hacer las pruebas en un ambiente de QA y tomar los recaudos necesarios de copias de seguridad.

lunes, 24 de febrero de 2014

¿Cómo darle estilo a la primera línea de un DIV?

Tenemos una vista en SharePoint 2007 que nos muestra varias columnas. La columna 4 es un campo de varias líneas de texto. Cuando vemos su contenido HTML, vemos que es un DIV con varias líneas separadas por BR.

¿Cómo hacemos para darle estilo a la primer línea?

La respuesta es con un poco de jquery, usando algunos trucos:
  • Por un lado usamos el atributo summary para identificar la vista.
  • Luego agregamos span a cada línea
  • Y luego le damos estilo al primer span

Aquí el código:

$('table [summary=Preguntas frecuentes] tr td:nth-child(4) div')
  .contents()
    .filter(function() {
      return this.nodeType === 3;
    })
      .wrap( "<span></span>" )
      .end()
    .filter( "br" )
//    .remove()
    ;
$('table [summary=Preguntas frecuentes] tr td:nth-child(4) div span:nth-child(1)').css('font-weight','bold');

Y aquí el resultado:


Espero les sea útil!

lunes, 17 de febrero de 2014

domingo, 9 de febrero de 2014

Ejemplos de CAML en Client Object Model (JavaScript)

Introducción

El modelo de objetos de cliente de SharePoint ha sido una gran incorporación en SharePoint 2010. Nos permite consultar información de listas, utilizando sólo un poco de código JavaScript. No se requiere desarrollo a nivel de servidor, ni permisos especiales en la granja de SharePoint.

Uno de los usos más comunes, que al menos yo le he dado, es consultar información de listas, para luego desplegarla  utilizando diferentes formas de presentación gráfica. Para ello, suelo utilizar consultas en lenguaje CAML. CAML tiene sus particularidades. En este breve artículo voy a presentar algunos ejemplos que me han resultado útiles.

En caso que nunca hayan utilizado Client Objetc Model, quizá sea conveniente que lean una introducción: http://surpoint.blogspot.com/2011/10/introduccion-al-modelo-de-objetos-de.html

 

Caso 1: empezando…

En el primer ejemplo, vamos a mostrar un caso completo. En los siguientes mostraremos sólo las variaciones. Imaginemos que necesitamos hacer una consulta simple, en donde veremos:

·         Cómo ordenar elementos

·         Cómo indicar qué columnas mostrar

·         Cómo indicar que queremos traer sólo una fila

 

function cargar_noticia_destacada() {

 contexto = new SP.ClientContext.get_current();

 var lista = contexto.get_site().get_rootWeb().get_lists().getByTitle('Noticias');

 var query = new SP.CamlQuery();

 var consulta =

  '<View>' +

  // Ordenada por fecha de cración (ID) descendente

  '<Query><OrderBy><FieldRef Name ="ID" Ascending="FALSE"/></OrderBy></Query>' +

  // Una sóla noticia

  '<RowLimit>1</RowLimit>' +

  '<ViewFields>' + 

  '<FieldRef Name="ID"/><FieldRef Name="Title"/><FieldRef Name="Resumen"/><FieldRef Name="Imagen_x0020_destacada"/>' +

  '</ViewFields>' +

  '</View>';

 query.set_viewXml(consulta);

 this.items = lista.getItems(query);

 contexto.load(items);

 contexto.executeQueryAsync(Function.createDelegate(this, this.cargaok_noticia_destacada), Function.createDelegate(this, this.cargamal));

}

 

En la siguiente función vemos  cómo procesar los ítems leídos. Esto por supuesto depende de nuestra página, pero lo muestro para que tengan un ejemplo de cómo se haría.

 

function cargaok_noticia_destacada(sender, args) {

var itemslista = this.items.getEnumerator();

while (itemslista.moveNext()) {

 var elitem = itemslista.get_current();

 var strHtmlImg  = elitem.get_item('Imagen_x0020_destacada').get_url();  

 var strHtmlTxt  ='<span class="h1">&gt; ' + elitem.get_item('Title') + '</span><br /><br />' + elitem.get_item('Resumen');

 var strHtmlBtn2 ='<a target="_self" href="javascript: AbrirVentanaModal(\'' + window.location.href.replace("/default.aspx","") + '/lists/Noticias/DispForm.aspx?ID=' + elitem.get_item('ID') + '&source=' + window.location.href + '\');"/>';  

 var strHtmlBtn1 ="<a href='" + window.location.href.replace("/default.aspx","") + '/lists/Noticias/' + "'/>";  

 }

// Imagen

$("#general_modulo1_img img").attr("src",strHtmlImg);

// Título y resumen

document.getElementById("general_modulo1_txt").innerHTML = strHtmlTxt  ;

// Noticia completa

$('#general_modulo1_btn td:nth-child(2) img').each(function() {

$(this).wrap(strHtmlBtn2);

       });

// Más noticias

$('#general_modulo1_btn td:nth-child(1) img').each(function() {

    $(this).wrap(strHtmlBtn1 );

});

$('#general_modulo1_btn td:nth-child(1) img').css("border","none");

}

 

Y finalmente nuestra rutina de error:

function cargamal(sender, args) {

alert('ERROR: ' + args.get_message() + '\n' + args.get_stackTrace());

}

 

Caso 2: agregando una condición

En este segundo ejemplo, vamos a ver cómo agregar una condición de igual sobre un campo de tipo texto:

'<Query><Where><Eq><FieldRef Name="Mostrar_x0020_en_x0020_p_x00e1_g"/><Value Type="Text">Si</Value></Eq></Where><OrderBy><FieldRef Name ="ID" Ascending="FALSE"/></OrderBy></Query>'

 

Caso 3: filtro por fecha actual

Supongamos ahora que queremos filtrar un campo fecha, comparándolo con la fecha del sistema. Veremos que necesitamos indicar el tipo de campo, e indicarle que no compare usando la hora:

'<Query><Where><Eq><FieldRef Name="Fecha"/><Value Type="DateTime" IncludeTimeValue="FALSE"><Today /></Value></Eq></Where></Query>'

 

Caso 4: filtrando entre fechas

Nuevamente compararemos con la fecha de sistema, pero esta vez, una comparación entre dos fechas:

'<Query><Where><And><Leq><FieldRef Name="Desde"/><Value Type="DateTime" IncludeTimeValue="FALSE"><Today/></Value></Leq><Geq><FieldRef Name="Hasta"/><Value Type="DateTime" IncludeTimeValue="FALSE"><Today/></Value></Geq></And></Where><OrderBy><FieldRef Name ="ID" Ascending="FALSE"/></OrderBy></Query>'

 

Caso 5: filtro por campo calculado

En el siguiente ejemplo, mostraremos cómo filtrar cuando nuestra columna es un campo calculado:

'<Query><Where><Eq><FieldRef Name="Apellido_x0020_y_x0020_nombre" /><Value Type="Computed">' + persona_seleccionada + '</Value></Eq></Where></Query>'

                              

Caso 6: filtro por campo de búsqueda

En este otro caso, vemos como filtrar en campos de búsqueda:

'<Query><Where><Eq><FieldRef Name="Usuario" LookupId="TRUE"/><Value Type="Lookup">' + usuario_conectado + '</Value></Eq></Where></Query>'

 

Caso 7: fecha ingresada por pantalla

Finalmente veremos un caso en donde necesitamos comparar contra una fecha ingresada por pantalla. El truco aquí es el formato de la fecha, el cual debemos formatear así:

var desde = ISODateString(Pdesde);

var hasta = ISODateString(Phasta);

 

La función utilizada es:

function ISODateString(d){

  function pad(n){return n<10 ? '0'+n : n}

  return d.getUTCFullYear()+'-'

      + pad(d.getUTCMonth()+1)+'-'

      + pad(d.getUTCDate())+'T'

      + pad(d.getUTCHours())+':'

      + pad(d.getUTCMinutes())+':'

      + pad(d.getUTCSeconds())+'Z'

}

Por último, el CAML para comparar las fechas ingresadas con las registradas en la lista:

'<Query><Where><And><Leq><FieldRef Name="Fecha"/><Value Type="DateTime" IncludeTimeValue="TRUE">' + hasta + '</Value></Leq><Geq><FieldRef Name="Fecha"/><Value Type="DateTime" IncludeTimeValue="FALSE">' + desde + '</Value></Geq></And></Where></Query>'

 

Conclusión

La utilización de Client Object Model de JavaScript nos brinda una forma muy sencilla de consultar información y mostrarla en una página de diferentes formas. En este breve artículo hemos mostrado algunas variantes del lenguaje de consulta CAML para entender cómo se aplican diferentes condiciones. Espero que les haya resultado útil. Hasta la próxima!

 

 

Juan Pablo Pussacq Laborde

SharePoint MVP

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

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

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

 

Artículo publicado originalmente en: http://www.compartimoss.com/revistas/numero-17/ejemplos-caml-client-object-model-javascript

lunes, 3 de febrero de 2014

Modificar los enlaces de navegación en SharePoint 2007 vía programación

Supongamos que tenemos que modificar en forma masiva los enlaces de navegación de nuestro sitio. Por ejemplo, para corregir un error típico como puede ser la eliminación de direcciones absolutas.



Este es un requerimiento que puede resolverse con facilidad vía programación, a través de una página o una aplicación de consola. En este ejemplo, les muestro cómo hacerlo en una página ASPX para todos los sitios de la colección activa.

Espero les resulte útil !

<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Page Language="C#"
         Inherits="Microsoft.SharePoint.WebControls.LayoutsPageBase" EnableViewStateMac="false" EnableViewState="true" MaintainScrollPositionOnPostback="true"%>

<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.Navigation" %>

<script runat="server">

protected override void OnLoad(EventArgs e) {

string reemplazar = "http://misitio.com.ar";
string modificado;
string resultado = "<table><tr><td>It</td><td>Nombre</td><td>Url</td></tr>";

SPSite SI = SPContext.Current.Site;
SPWebCollection WEBS = SI.AllWebs;

foreach (SPWeb WE in WEBS) {

WE.AllowUnsafeUpdates = true;

resultado += "<tr><td><strong>" +  WE.Url + "</strong></td></tr>";

foreach (SPNavigationNode NODO in WE.Navigation.TopNavigationBar) {

resultado += "<tr><td>" +  NODO.Id + "</td><td>" +  NODO.Title + "</td><td>" +  NODO.Url + "</td></tr>";

if (NODO.Url.IndexOf(reemplazar) != -1) {
modificado = NODO.Url.Replace(reemplazar,"");

NODO.Url = modificado;
NODO.Update();

resultado += "<tr><td style='color:red;'>" +  modificado + "</td></tr>";
}

}

WE.Update();
WE.AllowUnsafeUpdates = false;

}


Response.ContentType = "text/plain";
Response.Write(resultado);


}

</script>

Leer y almacenar propiedades de perfiles de usuario en SharePoint 2007 vía programación

En este breve articulo explicaremos cómo resolver un requerimiento en donde nos piden mover las propiedades de los perfiles de usuario de SharePoint 2007, de un servidor a otro. El enfoque que seguiremos es el siguiente:

  • Creamos una página ASPX que lea el contenido
  • Copiamos el contenido y lo pegamos dentro de un Excel
  • Copiamos el contenido y lo pegamos en una Lista
  • Creamos una página ASPX que lea el contenido de la lista y lo almacene en el servidor destino
Utilizamos una línea intermedia, porque por un tema de seguridad, los servidores no se ven entre sí.


Creamos una página ASPX que lea el contenido

<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Assembly Name="Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Page Language="C#" 
         Inherits="Microsoft.SharePoint.WebControls.LayoutsPageBase" EnableViewStateMac="false" EnableViewState="true" MaintainScrollPositionOnPostback="true"%>

<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.Office.Server" %>
<%@ Import Namespace="Microsoft.Office.Server.UserProfiles" %>

<script runat="server">

protected override void OnLoad(EventArgs e) {


// Acceso al Sitio
ServerContext CTX = ServerContext.GetContext(this.Site);

// Loop Usuarios
UserProfileManager UPM = new UserProfileManager(CTX);

string resultado = "<table><tr><td>Cuenta</td><td>Foto</td><td>Web</td></tr>";

foreach (UserProfile UP in UPM) {

resultado += "<tr><td>" + UP["Accountname"].Value + "</td><td>" + UP["PictureUrl"].Value + "</td><td>" +  UP["PersonalSpace"].Value + "</td></tr>";

        }

resultado += "</table>";

// Impresión de Resultado
        
Response.ContentType = "text/plain";
Response.Write(resultado);

}

</script>


Copiamos el contenido y lo pegamos dentro de un Excel
Esta acción es un simple copiar y pegar. Pero es un buen momento para arreglar contenido o direcciones absolutas en caso que lo necesitemos.


Copiamos el contenido y lo pegamos en una Lista
Creamos una lista llamada UP con los campos que necesitemos, en nuestro caso: Título (para el nombre de cuenta), Foto y Web. Todos campos de texto simple.

Creamos una página ASPX que lea el contenido de la lista y lo almacene en el servidor destino

<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Assembly Name="Microsoft.Office.Server, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Page Language="C#" 
         Inherits="Microsoft.SharePoint.WebControls.LayoutsPageBase" EnableViewStateMac="false" EnableViewState="true" MaintainScrollPositionOnPostback="true"%>

<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.Office.Server" %>
<%@ Import Namespace="Microsoft.Office.Server.UserProfiles" %>

<script runat="server">

protected override void OnLoad(EventArgs e) {

string resultado = "<table><tr><td>Cuenta</td><td>Foto</td><td>Web</td></tr>";
string foto;
string web;
string cuenta;
int ok = 0;
int nook = 0;

// Acceso al Sitio
ServerContext CTX = ServerContext.GetContext(this.Site);

// Leer y almacenar los datos de perfiles de usuario

SPWeb WE = SPContext.Current.Web;
SPList LI = WE.Lists["UP"];

WE.AllowUnsafeUpdates = true;

UserProfileManager UPM = new UserProfileManager(CTX);

foreach (SPListItem IT in LI.Items){

if (IT["Foto"] != null) { foto = IT["Foto"].ToString(); } else { foto = "" ; }
if (IT["Web"] != null) { web = IT["Web"].ToString(); } else { web = "" ; }

cuenta = IT["Title"].ToString();
if ( UPM.UserExists (cuenta) ) {

UserProfile UP = UPM.GetUserProfile(cuenta);

                if (UP["PictureUrl"] != null)
                {
UP["PictureUrl"].Value = foto;
               }

                UP["PersonalSpace"].Value = web;

UP.Commit();

ok += 1;
}
else {
nook += 1;
}

resultado += "<tr><td>" + IT["Title"].ToString() + "</td><td>" + foto + "</td><td>" +  web + "</td></tr>";

        } 

WE.AllowUnsafeUpdates = false;

resultado += "</table>";
resultado += "<strong>Perfiles encontrados: " + ok.ToString() + "</strong>";
resultado += "<br/><strong>Perfiles NO encontrados: " + nook.ToString() + "</strong>";

        
Response.ContentType = "text/plain";
Response.Write(resultado);

}


</script>

Finalmente, podemos revisar los resultados dentro del SSP:



Espero les haya resultado útil. Hasta la próxima!