Primeros pasos con SASS

Muy buenas a todos,

Ya se ha pasado el verano, y ya se han acabado las vacaciones, así que toca ponerse de nuevo manos a la obra. Durante este verano he dado un respiro al blog, que me ha ocupado bastante tiempo durante el año, mantenerlo y potenciarlo requiere un esfuerzo importante, así que tocaba descanso. Pero ya se ha terminado, a partir de ahora, iré recuperando el ritmo del blog, compartiendo con todo el mundo los conocimientos que voy adquiriendo e incluyendo en mi día a día.

Para empezar quiero compartir con vosotros mis primeros pasos con una herramienta que nos permite optimizar y mejorar el entendimiento y el trabajo con nuestras hojas de estilo, y no es otra cosa que SASS. Lo que hoy os quiero contar, a parte de hacer una breve introducción a SASS para aquellos que no lo conocen, son algunas de las características que podemos usar en nuestras hojas de estilo, y que a mi en concreto, al venir de un perfil más de programador, me ha ayudado a tratar las hojas de estilo de una forma más familiar, haciendo uso de variables, bucles, funciones, etc. Como veremos más adelante.

Os pongo el primer lugar el enlace de la página oficial de SASS, donde podréis encontrar toda la documentación sobre esta extensión de CSS.

http://sass-lang.com/

En la misma página podéis encontrar la siguiente definición sobre SASS que reproduzco a continuación para aclarar, para aquellos que no lo sepan, lo que es SASS exactamente.

Sass is an extension of CSS that adds power and elegance to the basic language. It allows you to use variables, nested rules, mixins, inline imports, and more, all with a fully CSS-compatible syntax. Sass helps keep large stylesheets well-organized, and get small stylesheets up and running quickly

Sass es una extensión de CSS que añade poder y elegancia al lenguaje básico. Nos permite utilizar variables, reglas anidadas, mixins, importaciones y más, todo ello completamente compatible con la sintaxis CSS. SASS ayuda a mantener grandes hojas de estilo bien organizadas y poner en marcha pequeñas hojas de estilo rápidamente.

Hoy os quiero enseñar algunas de las funcionalidades que incluye esta extensión de CSS y cómo podemos utilizarlas y para qué las he usado yo concretamente, motivos por los cuales me ha resultado muy interesante conocer y utilizar SASS en mis proyectos.

Variables

Con Sass podemos definir variables que después podremos utilizar como valores de las propiedades en nuestro CSS. Definiremos y utilizaremos una variables de la siguiente forma:

$color: red;

.box{
    color:$color;
}

Para cualquier programador, es inevitable, comprender las ventajas de usar variables en el CSS, nos permite modificar un valor que se repite en muchas clases de nuestro CSS con único cambio.

Reglas anidadas

Otra de las características que nos hacen el código CSS más entendible por medio de SASS, es la forma en que nos permite anidar las clases y las definiciones de elementos CSS. Esto asemeja el código mucho más a la estructura jerárquica que tiene HTML y por tanto más entendible. Vamos a ver la diferencia entre la forma tradicional de anidar clases en CSS y cómo podríamos hacerlo con SASS

/*CSS*/
ul li .example{
   /*propiedades CSS */
}

ul li a{
   /*propiedades CSS */
}

/*SASS*/
ul{
      
   li{
      .example{
          /*propiedades CSS*/
      }
      
      a{
          /*propiedades CSS
      }
   }
}

Mixins

Otro de los aspectos más útiles que incorpora SASS son los mixins. Desde un punto de vista práctico, los podemos definir como funciones que nos permiten agrupar propiedades CSS, además pueden incorporar parámetros para hacerlos más flexibles. Un mixin se define y se usa de la siguiente forma:

@mixin mixinexample() {
  /*Propiedades dentro del mixin*/
}

@mixin mixinexample2($ejemplo) {
  /*Propiedades dentro del mixin*/
  color:$ejemplo
}
.clase{
   @include mixinexample();
   @include mixinexample2(red);
}

Un ejemplo de un mixin que he creado recientemente en uno de mis proyectos, me sirve para colocar imágenes de background en mi CSS, hay una serie de propiedades que se repetían cada vez que quería colocar una imagen, y finalmente las agrupé en un mixin.

@mixin background-image($url,$repeater,$position,$size) {
  background-image:$url;
  background-repeat:$repeater;
  background-position:$position;
  background-size:$size;
}

Bucles

Otras de las características que nos ofrece SASS son los bucles. Como cualquier lenguaje de programación nos permite definir bucles de tipo @for, @each, @while. Os dejo un enlace donde se explican cómo usarlos.

http://thesassway.com/intermediate/if-for-each-while

Los bucles nos pueden servir para crear clases recursivas en las que las propiedades CSS son similares, vamos a ver un ejemplo.


$lista: $tipo1 $tipo2 $tipo3 $tipo4

@each $tipo in $list
{
    .#{$tipo}{
        background-image: url('/images/#{$tipo}.png');
        color:red;
    }
}

Este ejemplo nos creará una clase por cada tipo en la lista. Estas clases se crean con las propiedades comunes, pero cambia la imagen de fondo en función del tipo.

Más adelante, en otro post, os enseñaré algunos ejemplos de otras opciones que nos ofrece SASS, que también son muy interesantes, como la herencia, las clases ocultas, condicionales, etc.

Y esto es todo por hoy, espero, como siempre, que os haya resultado interesante. Aaaah! por cierto, se me había olvidado comentar que el soporte para SASS se puede añadir fácilmente en Visual Studio 2013 a través de la extensión Web Essentials, aquí os dejo un enlace donde os da toda la información relativa a esto http://www.mattburkedev.com/using-sass-with-visual-studio-2013-essentials/

Un saludo, hasta la próxima.

Anuncios

Search Driven Development. Usando la API REST de SharePoint para el servicio de búsqueda

Muy buenas a todos,

Recientemente he publicado algunos artículos sobre Search Driven Development para SharePoint Online y SharePoint 2013. Además hace poco tiempo tuve la oportunidad de participar en una mesa redonda con la gente de MadPoint en la que se hablaba de técnicas de desarrollo tanto para SharePoint OnLine como 2013 y Office 365. En esta mesa redonda salió la posibilidad de usar en los desarrollos la API REST del servicio de búsquedas de SharePoint y los nuevos frameworks de Javascript para crear nuestra propia solución basada en búsquedas.

Hasta entonces no había tenido la oportunidad de probar esta API REST, pero hoy os traigo una entrada en la que os quiero enseñar como usar las búsquedas a través de la API REST que nos proporciona esta versión de SharePoint.

En una primera parte de la entrada os enseñaré que consultas REST podemos hacer al servicio y lo que nos devuelve y a continuación veremos ejemplos de cómo utilizar el mismo en una app for SharePoint (o SharePoint Add-ins??).

Llamadas REST para el servicio de búsqueda

Uno de los aspectos importantes que tenemos que tener en cuenta cuando trabajamos con la API REST del servicio de búsqueda es que las consultas al mismo podemos hacerlas a través de peticiones GET y a través de peticiones POST. Esto es así, porque como es sabido por todos, las peticiones GET tienen limitaciones en cuanto al número de caracteres y puede ocurrir que una consulta que queramos hacer sobrepase ese límite, por lo que tendremos la oportunidad de usar las peticiones POST para superar dicha restricción.

A continuación voy a enseñar algunos ejemplos básicos de cómo utilizar ambos tipos de peticiones, aunque las posibilidades que tenemos a la hora de hacer las peticiones son muy extensas. Para esto os dejo el enlace a la referencia de la MSDN, donde podréis analizar al completo las opciones de la API.

https://msdn.microsoft.com/es-es/library/office/jj163876.aspx

Usando peticiones GET

Las peticiones GET se hacen a través de la siguiente URL:

http://servidor/_api/search/query

Consulta con un texto a buscar
/_api/search/query?querytext=’textoAconsultar’
Consulta usando una query
/_api/search/query?querytemplate='{searchterms} FileExtension: doc’
Usando la ordenación
/_api/search/query?querytext=’textAconsultar’&sortlist=’created:ascending,rank:descending,’
Indicando los refinadores a utilizar
/_api/search/query?querytext=’textAconsultar’&refiners=’author,size,fileExtension’
Filtrando la consulta usando el refinamiento
/_api/search/query?querytext=’textAconsultar’&refinementfilters=’fileExtension:equals(“docx”)’

Usando peticiones POST

Las peticiones POST se hacen a través de la siguiente URL:

http://servidor/_api/search/postquery

Consulta con un texto a buscar
{'request':
    { 
      '__metadata' : {'type' : 
                   'Microsoft.Office.Server.Search.REST.SearchRequest'},
      'Querytext' : 'ejemploAconsultar'
    }
}

Son muy importantes las mayúsculas y minúsculas en las consultas usando el método POST. Por ejemplo, en la consulta anterior, tenemos que usar ‘Querytext’ y no ‘QueryText’ o ‘querytext’ como hacemos en la consulta GET.

Estas consultas devuelven un JSON con toda la información necesaria de la búsqueda. Al igual que el resto de servicios REST, este de búsquedas es compatible con JSON Light, por lo que si lo deseeamos podemos obtener respuestas más ligeras y rápidas. Los resultados se encuentran en el objeto devuelto en:

data.body.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results (para GET)
data.body.d.postquery.PrimaryQueryResult.RelevantResult.Table.Rows.results (para POST)

Este elemento “results” json es un array que contiene una fila para cada uno de los resultados, en cada columna o celda de esta fila tenemos una de las propiedades del objeto devuelto. Para simplificar un poco la tarea, os voy a indicar en qué posición se encuentra la información que habitualmente es más importante de los objetos devueltos.

  • Posición 3: Title
  • Posición 4: Author
  • Posición 5: Size
  • Posición 6: Path
  • Posición 7: Description
  • Posición 8: Write
  • Posición 11: HitHighlightedSummary
  • Posición 18: FileExtension
  • Posición 31: FileType

El mismo servicio, si le hemos indicado para qué propiedades queremos obtener refinadores, nos devuelve los refinadores para esa búsqueda en:

data.body.d.query.PrimaryQueryResult.RefinementResults.Refiners.results

Esto nos devuelve, para cada refinador, un objeto de la siguiente forma:

{'Entries':
    {'results'
         {
             RefinementCount: count,
             RefinementToken: token,
             RefinementName: name,
             RefinementValue: value
         },
         {
             RefinementCount: count,
             RefinementToken: token,
             RefinementName: name,
             RefinementValue: value
         }
    },
  'Name':Refiner
}

Cada uno de los elementos contenidos en el objeto Entries, nos indica una opción de refinamiento para el refinado correspondiente. A continuación vamos a ver cómo he usado este servicio en una aplicación para SharePoint a modo de prueba.

Usando la API REST para búsquedas en una App for SharePoint

El ejemplo lo he realizado usando JQuery para manipular el DOM de la página del mismo. En primer lugar vamos a ver una captura de la aplicación para explicar que hace cada sección de la misma:

appsearchexample

En la aplicación que os quiero enseñar he hecho 5 ejemplos de como usar la API REST del servicio de búsqueda:

  • El primero ejemplo es una consulta usando GET a partir de una cadena de texto.
  • El segundo ejemplo es una consulta por POST a partir de una cadena de texto.
  • El tercer ejemplo usar una query para hacer la llamada al servicio de búsqueda por GET.
  • En el cuarto ejemplo se usa la ordenación.
  • El quinto ejemplo utiliza los refinadores.

Código del primer ejemplo

$("#searchbutton1").click(function () {

        var searchText = $("#searchbox1").val();

        executor.executeAsync({
            method: "GET",
            url: appweburl + "/_api/search/query?querytext='" + searchText + "'",
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose"
            },
            success: function (data) {

                var element = document.getElementById("resultsList1");

                $('#resultsList1 > li').remove();

                var jsonObject = JSON.parse(data.body);

                console.log(jsonObject);

                var results = jsonObject.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;

                for(var i = 0; i < results.length; i++)
                {
                    var li = document.createElement("li");

                    li.innerText = results[i].Cells.results[3].Value + "
" + results[i].Cells.results[5].Value + "&nbsp;" + results[i].Cells.results[4].Value + "
" + results[i].Cells.results[11].Value;

                    element.appendChild(li);
                }
            },
            error: function (data) {
            }
        });

        return false;

    });

Código del segundo ejemplo

$("#searchbutton2").click(function () {

        var searchText = $("#searchbox2").val();

        executor.executeAsync({
            method: "POST",
            url: appweburl + "/_api/search/postquery",
            body: "{'request': { '__metadata' : {'type' : 'Microsoft.Office.Server.Search.REST.SearchRequest'}, 'Querytext' : '" + searchText + "' }}",
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose"
            },
            success: function (data) {

                var element = document.getElementById("resultsList2");

                $('#resultsList2 > li').remove();

                var jsonObject = JSON.parse(data.body);
                var results = jsonObject.d.postquery.PrimaryQueryResult.RelevantResults.Table.Rows.results;

                for (var i = 0; i < results.length; i++) {
                    var li = document.createElement("li");

                    li.innerText = results[i].Cells.results[3].Value;

                    element.appendChild(li);
                }
            },
            error: function (data) {
            }
        });

        return false;

    });

Código del tercer ejemplo

executor.executeAsync({
        method: "GET",
        url: appweburl + "/_api/search/query?querytemplate='{searchterms} FileExtension: doc'",
        headers: {
            "accept": "application/json;odata=verbose",
            "content-type": "application/json;odata=verbose"
        },
        success: function (data) {

            var element = document.getElementById("resultsList3");

            $('#resultsList3 > li').remove();

            var jsonObject = JSON.parse(data.body);

            var results = jsonObject.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;

            for (var i = 0; i < results.length; i++) {
                var li = document.createElement("li");

                li.innerText = results[i].Cells.results[3].Value;

                element.appendChild(li);
            }
        },
        error: function (data) {
        }
    });

Los tres primeros ejemplos son muy similares. Entre el primero y el segundo la única diferencia es que una petición se hace por GET y otra petición se hace por POST. En el tercero, se vuelve a usar una consulta por GET pero en este caso en lugar de un texto lo que se pasa es una consulta como tal. Todos los ejemplos usan una lista para representar los resultados devueltos.

Código del cuarto ejemplo

$("#searchbutton4").click(function () {

        var searchText = $("#searchbox4").val();

        executor.executeAsync({
            method: "POST",
            url: appweburl + "/_api/search/postquery",
            body: "{'request': { '__metadata' : {'type' : 'Microsoft.Office.Server.Search.REST.SearchRequest'}, 'Querytext' : '" + searchText + "' }}",
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose"
            },
            success: function (data) {

                var element = document.getElementById("resultsList4");

                $('#resultsList4 > li').remove();

                var jsonObject = JSON.parse(data.body);
                var results = jsonObject.d.postquery.PrimaryQueryResult.RelevantResults.Table.Rows.results;

                for (var i = 0; i < results.length; i++) {
                    var li = document.createElement("li");

                    li.innerText = results[i].Cells.results[3].Value;

                    element.appendChild(li);
                }
            },
            error: function (data) {
            }
        });

        return false;

    });

    $("#orderbutton4").change(function () {

        var searchText = $("#searchbox4").val();
        var value = $("#orderbutton4").val();
        var order = "";

        if(value == "asc")
        {
            order = "created:ascending";
        }
        else
        {
            order = "created:descending";
        }

        executor.executeAsync({
            method: "GET",
            url: appweburl + "/_api/search/query?querytext='" + searchText + "'&sortlist='" + order + "'",
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose"
            },
            success: function (data) {

                var element = document.getElementById("resultsList4");

                $('#resultsList4 > li').remove();

                var jsonObject = JSON.parse(data.body);

                var results = jsonObject.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;

                for (var i = 0; i < results.length; i++) {
                    var li = document.createElement("li");

                    li.innerText = results[i].Cells.results[3].Value + " " + results[i].Cells.results[8].Value;

                    element.appendChild(li);
                }
            },
            error: function (data) {
            }
        });

        return false;
    });

Básicamente este ejemplo funciona de la misma forma que los anteriores. Lo único extraordinario, tiene que ver con que queremos usar la ordenación por la propiedad “size” de los resultados. Hemos creado un combo donde podemos seleccionar, si queremos, orden ascendente y descendente para el tamaño. Lo que se ha hecho es crear otro evento que se dispara cuando cambia el valor seleccionado del combo para que se haga la ordenación. Para ello se añade a la consulta la parte correspondiente (sortlist) en función de si hemos seleccionado ascendente o descendente.

Código del último ejemplo

$("#searchbutton5").click(function () {

        var searchText = $("#searchbox5").val();

        executor.executeAsync({
            method: "GET",
            url: appweburl + "/_api/search/query?querytext='" + searchText + "'&refiners='author,size'",
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose"
            },
            success: function (data) {

                var element = document.getElementById("resultsList5");

                $('#resultsList5 > li').remove();

                var jsonObject = JSON.parse(data.body);

                console.log(jsonObject);

                var refiners = jsonObject.d.query.PrimaryQueryResult.RefinementResults.Refiners.results;

                $("#titlerefiner").text(refiners[0].Name);

                $.each(refiners[0].Entries.results, function (i, item) {
                    $('#valuerefiners').append($('<option>', {
                        value: refiners[0].Name + ":" + item.RefinementToken,
                        text: item.RefinementName
                    }));
                });

                var results = jsonObject.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;

                for (var i = 0; i < results.length; i++) {
                    var li = document.createElement("li");

                    li.innerText = results[i].Cells.results[3].Value;

                    element.appendChild(li);
                }
            },
            error: function (data) {
            }
        });

        return false;

    });

    $("#valuerefiners").change(function () {

        var searchText = $("#searchbox5").val();
        var value = $("#valuerefiners").val();

        executor.executeAsync({
            method: "GET",
            url: appweburl + "/_api/search/query?querytext='" + searchText + "'&refinementfilters='" + value + "'",
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose"
            },
            success: function (data) {

                var element = document.getElementById("resultsList5");

                $('#resultsList5 > li').remove();

                var jsonObject = JSON.parse(data.body);

                var results = jsonObject.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;

                for (var i = 0; i < results.length; i++) {
                    var li = document.createElement("li");

                    li.innerText = results[i].Cells.results[3].Value;

                    element.appendChild(li);
                }
            },
            error: function (data) {
            }
        });

        return false;
    });

Este ejemplo igualmente es similar en cuanto a la forma en la que se representan los datos a los anteriores. La diferencia aquí es que a la petición le estamos indicando las propiedades de los resultados obtenidos por las que queremos poder refinar (Línea 7). Una vez que se obtienen los resultados, se carga uno de los refinadores en un combo preparado para tal efecto (Líneas 22 a 31), este combo se carga automáticamente con los valores de refinamiento obtenidos.

Además se añade otro evento para cuando ese combo cambia, con la selección de un refinamiento, de manera que se haga el filtrado correspondiente. Para ello, solo se añade a la petición el refinementfilters con el contenido a filtrar (Línea 58).

Conclusiones

Trabajar con las búsquedas de SharePoint OnLine y 2013 nos ofrece una gran cantidad de oportunidades a la hora de desarrollar de una forma sencilla, la API REST del servicio de búsqueda es muy potente y merece la pena tenerla en cuenta. En la entrada de hoy, os he introducido al uso de la misma y os he mostrado un ejemplo de cómo usarla en una ejemplo de una aplicación para SharePoint.

Y esto es todo por hoy, espero que os resulte interesante. ¿Cuál es el siguiente paso?, bueno, como sabéis me gusta mucho Polymer, y creo que podría ser interesante crear un componente Polymer que use este servicio de búsqueda de forma completa. Espero en breve compartirlo con vosotros y ponerlo disponible para que lo podáis utilizar.

Un saludo y hasta la próxima

Search Driven Development (IV): Cross-site publishing

Muy buenas a todos,

Tarde de lluvia aquí en Madrid y que mejor que ponerse a escribir en el blog para contar algunas cosas interesantes :). Hace unos meses escribí una serie de artículos en la que hablaba sobre “Search Driven Development”, como configurar nuestros sitios, la navegación basada en metadatos y todos los aspectos necesarios para tener un catálogo y elementos de ese catálogo sin escribir código. Aquí os dejo los enlaces en los que hablaba de todos esos temas.

Primeros Pasos con SharePoint OnLine: Search Driven Development

Search Driven Development en SharePoint (II): Catálogos y Elementos de Catálogo

Search Driven Development (III): El WebPart de Refinamiento

Preparando una ejemplo para una mesa redonda en la que colaboraré con la gente de MadPoint el próximo 7 de Mayo, he decidido retomar el artículo que me faltaba en esta serie y aquí os lo traigo. Me había quedado pendiente montar un entorno de cross-site publishing en el que en un sitio de publicación se publicarán los elementos del catálogo y en un sitio de consumo, se pudieran, usando los webpart de búsqueda, consumir esos elementos, incluyendo URLs amigables y navegación basada en metadatos. Todo esto puede parecer muy complejo, y que necesitará de muchísima configuración, pero ya veréis que es algo muy sencillo. La estructura que queremos conseguir es como la siguiente:

crosssiteschem

Esta característica de cross-site publishing es otra de las nuevas e interesantes funcionalidades que nos ofrece SharePoint 2013. Para conseguir esta configuración, vamos a llevar a cabo una serie de pasos que veremos a continuación, y que podremos aplicar tanto a SharePoint OnLine como SharePoint 2013 en su versión On-Premises. El objetivo de nuestro ejemplo, será crear un sitio de consumo que nos permita organizar una serie de productos de un catálogo en base a categorías y elementos que pertenecen a cada una de dichas categorías, y que dichas categorías sean las que definan la navegación de nuestro sitio, además los elementos serán publicados desde un sitio de publicación.

Creando las colecciones de sitios

El primer paso es crear las colecciones de sitios que vamos a utilizar en el ejemplo. En este caso crearemos dos, una primera de publicación y otra de consumo. Ambas colecciones de sitios las crearé bajo la plantilla de publicación, yo he llamado a una publicador y otro consumidor.

Las colecciones de sitio se crean de forma relativamente distintas si estamos en SharePoint OnLine o SharePoint 2013, yo las he creado en SharePoint OnLine para el ejemplo.

Captura de pantalla 2015-04-26 a las 20.19.13

Creando el almacén de términos

El siguiente paso, es definir el almacén de términos que va a categorizar los elementos que forman parte de nuestro catálogo, para ello accedemos a la administración de nuestro almacén de términos, que como sabéis está disponible tanto desde la administración central (o el panel de administración de SharePoint OnLine) como desde la configuración del sitio, y creamos la estructura, tiene que quedar de una forma similar a la siguiente:

Captura de pantalla 2015-04-26 a las 20.34.23

Configurando el sitio de publicación

Una vez que ya tenemos la categorización que tendrá nuestro catálogo de productos vamos a configurar el sitio de publicación con todos los elementos necesarios. Lo que vamos a hacer en el este sitio es lo siguiente:

  • El primer paso es activar en las “Características de la colección de sitios”, la característica “Publicación de colecciones entre sitios”.
  • Crear cuatro columnas de sitio: Una llamada Resumen de varias líneas de text, otra FechaPublicacion de tipo Fecha, otra llamada Clasificación de tipo metadatos administrados asociada al almacén creado en el apartado anterior y otras llamada autores que será de una línea de texto.
  • Crear un tipo de contenido que herede del Tipo de Contenido Elemento y al que se asocien las cuatro columnas anteriores y que se llamará “Libros”.
  • Crear una lista llamada “Productos”, para la que habilitaremos en la configuración de la misma la Administración de tipos de contenido y le asociaremos el tipo de contenido que acabamos de crear

Con esto ya tendremos preparado nuestro sitio de publicación. Este puede ser el momento para añadir algunos elementos a la lista que hemos creado también.

Creando el catálogo del sitio de publicación

Antes de pasar a la configuración del sitio de consumo, vamos a habilitar la lista para que pueda ser utilizada como catálogo. Para ello, dentro de la lista “Productos” que hemos creado, vamos a Configuración de la lista->Configuración del catálogo, esto nos llevará hasta la página que vemos a continuación.

Captura de pantalla 2015-04-26 a las 21.04.30

Aquí tendremos que indicar varias cosas, por un lado indicar que queremos habilitar esta lista para ser un catálogo, a continuación también indicaremos que columnas de la lista queremos que formen parte de la url del catálogo, y por último el campo, que normalmente se carga automáticamente, que establecerá la jerarquía de navegación. Una vez completados todos los aspectos de configuración, pulsamos en aceptar. Ahora ya podremos pasar a configurar el sitio de consumo. No obstante antes, en el apartado de Configuración Avanzada en la configuración de la lista indicaremos que se vuelva a indexar la lista en el próximo rastreo.

Configurando el sitio de consumo

Para terminar, nos vamos a la colección de sitios que hemos creado para trabajar como consumidor. Para configurar esta colección de sitios nos vamos a la configuración del sitios y entramos en Administrar conexiones del catálogo.

Captura de pantalla 2015-04-26 a las 21.13.46

Cuando entramos en esta opción pulsamos sobre Conectar a un catálogo y después seleccionamos la opción del catálogo que creamos en el sitio de publicación, observaremos que tenemos esa opción disponible.

managercatalog

Si todo lo hemos configurado correctamente, solo tendremos que configurar un aspecto, y es la raíz de la jerarquía de elementos, en donde tendremos que indicar la misma desde nuestro almacén de términos. Tras esto pulsamos sobre Aceptar y automáticamente todo estará funcionando correctamente como vemos en la figura siguiente.

Captura de pantalla 2015-04-26 a las 21.27.38

Como se puede observar automáticamente se ha creado toda la estructura de navegación usando el almacén de términos que habíamos creado. Además si accedemos, vemos que podemos entrar a todos los elementos de cada categoría, o si entramos a alguno de ellos al detalle. Es decir, configurando el catálogo toda la configuración de los webparts de búsqueda y de la navegación basada en metadatos se ha realizado automáticamente.

Captura de pantalla 2015-04-26 a las 21.32.24

Captura de pantalla 2015-04-26 a las 21.32.36

Lo único que faltaría sería usar los Display Templates para dar formato a las visualizaciones de los elementos. Para ello se puede usar lo que ya comenté en la entrada del post sobre ese tema:

Introducción al uso de Display Templates en SharePoint OnLine

Os dejo también un enlace con un tutorial muy detallado y completo sobre cómo configurar una solución Cross-Site Publishing y Search Driven Development.

http://blogs.technet.com/b/tothesharepoint/archive/2013/02/20/stage-1-create-site-collections-for-cross-site-publishing.aspx

Espero que os haya resultado interesante. Como os digo en la mesa redonda del evento que organiza MadPoint el próximo 7 de Mayo, mostraré el ejemplo completo funcionando, donde usaré ya los DisplayTemplates.

Os dejo también el enlace del evento por si alguien se quiere apuntar, se hablará no solo de SSD, sino de todas las formas de extender SharePoint OnLine y On-Premises, creo que va a ser muy interesante.

http://www.madpoint.net/2015/04/22/ciclo-de-mesas-redondas-opciones-de-desarrollo-en-sharepoint-y-office-365/

Un saludo a todos.

Webinar sobre “Ciclo de vida de una solución SharePoint”

Muy buenas tardes a todos,

Quería compartir con vosotros un enlace a un webinar que pude presenciar la pasada semana y que me parece de un interés capital para todas las personas que profesionalmente nos dedicamos a desarrollar sobre SharePoint. Dicho webinar estuvo dirigido por los MVPs de Encamina Alberto Díaz, Santiago Porras y Adrián Díaz, y nos estuvieron desvelando algunos de los aspectos más importantes a tener en cuenta durante el ciclo de vida de un proyecto de SharePoint y nos contaron los retos que se presentan y buenas prácticas en las distintas etapas de los mismos. Como os decía, algo capital para todos los que nos dedicamos al desarrollo de proyectos sobre SharePoint.

En el webinar hablaron de distintas etapas: Gestión de requisitos, Arquitectura de la solución, Desarrollo, Diseño, Construir y empaquetar, Calidad, Testing, Despliegue y Operaciones.

Simplemente os dejaré algunos consejos y “buenas prácticas” que he sacado del webinar.

  • El primer aspecto cuando estamos en la fase de requisitos es que hay que tener claro que estamos desarrollando sobre SharePoint, puede parecer algo muy obvio, pero todo el mundo implicado en el proyecto tiene que conocer el producto y lo que nos ofrece.
  • No se puede cambiar el producto y es importante, siempre que sea posible usar lo estándar y solo extenderlo en el caso de que nuestros requerimientos lo necesiten.
  • Usar patrones y buenas prácticas de desarrollo como haríamos para cualquier otro proyecto, porque en ese aspecto SharePoint no es diferente, se puede hacer sin ningún problema.
  • Usar una herramienta de control de código fuente.
  • Estandarizar los entornos de desarrollo por medio de herramientas tipo SPInstaller y conocer los requisitos de desarrollo y tenerlos en cuenta para todo el equipo de desarrollo.
  • Con respecto al diseño, hay que procurar homogeneizar el look & feel de páginas, los elementos y el comportamiento de los mismos dentro de nuestro proyecto. Tener una comunicación fluida y proactiva entre los equipos de desarrollo y diseñadores y algo muy importante, tratar los fallos de UX como defectos y no como mejoras en nuestros proyectos para evitar que caigan en el olvido.
  • Es importante hacer CodeReviews periódicas en las que se analice el código y se intente mejorar el mismo.
  • Sobre los desarrollos en SharePoint, se pueden y deben hacer Test unitarios. Evidentemente, habrá aspectos que no se pueden probar, como los elementos de la API, pero debemos testear la lógica de negocio de nuestra aplicación.
  • Establecer un proceso de despliegue de nuestras soluciones, que sea conocido y que por medio de PowerShell incluya también parametrizaciones, configuración y que todo quede registrado para poder hacer seguimiento. No usar SharePoint Designer para hacer modificaciones, o si se hace, convertirlo lo antes posible en un artefacto o elemento que pueda ser desplegable.

Os dejo el enlace del webinar que desde Encamina han dejado en su canal de Youtube y os recomiendo a todos que lo veáis porque os va a dar información de mucha utilidad.

Enlace al webinar de Ciclo de vida de una solución

Un saludo a todos. Nos vemos en breve con mas temas de interés sobre todo lo relacionado con Azure, SharePoint y Office 365 por el blog.

Usando Bower y Grunt en Visual Studio 2013

Muy buenas a todos,

Hoy os quiero contar los pasos que he dado para usar en mis proyectos de Visual Studio 2013 bower, usándolo como gestor de paquetes en el lado del cliente, y grunt, como task runner. Ambos están siendo muy comúnmente usados en el mundo del desarrollo de aplicaciones web y si bien, la versión de Visual Studio 2015 los incluye de forma nativa, podemos configurar nuestros proyectos web de Visual Studio 2013 para usar estas dos herramientas tan útiles.

Para todo aquel que le pueda interesar, dejo la entrada del blog en la que me he basado para hacer toda la configuración, aunque si accedéis veréis que en el post, se usa otro gestor de tareas llamado gulp en lugar de grunt.

http://blogs.msdn.com/b/cdndevs/archive/2015/02/17/using-bower-with-visual-studio-2013.aspx

Pero antes de entrar en detalle, vamos a ver qué son exactamente cada una de estas herramientas.

¿Qué es Bower?

Bower works by fetching and installing packages from all over, taking care of hunting, finding, downloading, and saving the stuff you’re looking for.

La primera pregunta que se nos plantea viendo lo que hace Bower es ¿Y Nugget?. La respuesta a esta pregunta es muy sencilla. Desde su aparición Nugget se ha descubierto como una herramienta muy eficaz para la gestión de paquetes de servidor, pero no así para paquetes de cliente. Bower se ha convertido en el estándar de facto en el desarrollo web. Es poco probable, que un desarrollador que crea una nueva librería en Javascript la ponga disponible como paquete Nugget, sin embargo es frecuente que esté disponible en Bower en todas sus versiones. Esto provoca desactualización o imposibilidad de usar determinadas librerías a través de Nugget, lo que desaconseja su uso como herramientas de gestión de paquetes del lado del cliente.

¿Qué es Grunt?

In one word: automation. The less work you have to do when performing repetitive tasks like minification, compilation, unit testing, linting, etc, the easier your job becomes.

En resumen, con Grunt vamos a poder automatizar determinadas tareas que van a ayudar a tener nuestra aplicación lista para poner en producción con acciones como: minimizar y agrupar los estilos, minimizar y agrupar los javascript, etc. En la entrada veremos una tarea que he creado que puede ser necesaria y recomendable cuando usamos Bower.

Configurando y usando Bower

Para la configuración de Bower, podéis fácilmente seguir la entrada del blog de la msdn sin ningún problema, es muy sencilla de seguir y está muy detallada, de hecho yo la he seguido y es lo que de forma simplificada explicaré por aquí, solo hay una cosa que cambia, porque a mi al menos, o no me quedaba claro en la entrada inicial o no estaba debidamente reflejado. Vamos a ello.

    1. El primer paso es instalar node.js, ya que tanto Bower como Grunt lo utilizan. Enlace de descarga de node.js
    2. A continuación, abrimos la línea de comandos e instalamos bower
npm install bower -g
    1. Tras esto, necesitaremos tener instalado Git, Bower utiliza Git para descargar los paquetes. Concretamente instalaremos msysgit. Durante la instalación, tendremos que seleccionar la opción que vemos en la imagen a continuación para que se realice toda la configuración correctamente

gitinstall

    1. Ya tenemos listo Bower para ser utilizado. Si lo vamos a usar dentro de un proyecto de MVC, antes tendremos que tener en cuenta que por defecto, la plantilla de este tipo de proyectos en Visual Studio incluye una serie de paquetes, entre ellos: bootstrap, jquery, modernizr, etc. Por lo que antes de comenzar a usar bower en nuestro proyecto, tenemos que desinstalarlos. Para ello accedemos a la consola de Nuget y ejecutamos el siguiente comando para cada uno de los paquetes
Uninstall-Package <package>

Ahora que ya tenemos listo Bower vamos a usarlo dentro de nuestro proyecto. Esto lo haremos a través de la línea de comandos. En mi caso, no he usado la interfaz de línea de comandos propia de Windows (cmd), ya que me daba errores al usarla con Bower y Git, y buscando por internet vi que recomendaban usar una interfaz de línea de comandos que se instala con msysgit. Esta es Git Bash.

Vamos ahora a entrar al directorio del proyecto, y comenzaremos con el uso de Bower. Lo primero que haremos será iniciar el fichero de configuración de Bower. Para ello ejecutamos:

bower init

Una vez creado el fichero bower.json, podemos comenzar a instalar paquetes, esto lo haremos de la siguiente forma.

bower install jquery --save
bower install polymer --save

Una Buena Práctica recomendada para el uso de los paquetes que se obtienen con Bower, es no agregar directamente todos los archivos descargados con Bower a nuestro proyecto, ya que los paquetes en muchas ocasiones contienen más archivos de los que realmente necesitaremos, por lo que se recomienda copiar al proyecto solo aquellos archivos que realmente sean necesarios. ¿Cómo hacemos esto, manualmente?. La respuesta evidentemente es no, para esto tenemos nuestro Task Runner, Grunt 😉

Configurando y usando Grunt

En primer lugar, para añadir el soporte a Grunt para nuestro proyecto vamos a crear dos ficheros que añadiremos a la raíz del mismo package.json y gruntfile.js. El fichero package.json inicialmente contendrá la siguiente información.

{
"name": "WebApplication1",
"version": "1.0.0",
"description": "WebApplication package project",
"main": "gruntfile.js",
"license": "ISC";
}

A continuación y como ejemplo, la tarea que vamos a crear va a copiar los ficheros necesarios de los paquetes que hemos obtenido mediante Bower a la carpeta Scripts, donde se encuentran los ficheros js de nuestro proyecto. El código del fichero gruntfile.js es el siguiente:

module.exports = function (grunt) {

    grunt.initConfig({
        copy: {
            main: {
                files: [
                  {
                      expand: true,
                      cwd: '../bower_components/webcomponentsjs/',
                      src: ['webcomponents.js'],
                      dest: 'Scripts'
                  },
                  {
                      expand: true,
                      cwd: '../bower_components/jquery/dist',
                      src: ['jquery.min.js'],
                      dest: 'Scripts'
                  }
                ]
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.registerTask('build', ['copy']);
};

Obviamente, aún podríamos hacer algunas tareas de minimización y agrupamiento de ficheros con grunt y que serían necesarias, aunque eso lo dejo para más adelante, es posible que en alguna entrada futura os cuente como hacerlo.

Para que funcione correctamente, necesitamos un plugin de Grunt, “grunt-contrib-copy”. Cuando necesitemos añadir determinados plugins para nuestras tareas de Grunt, lo haremos desde la línea de comandos usando la utilidad npm de node.js. Nos vamos al directorio del proyecto y ejecutamos el siguiente comando.

npm install grunt-contrib-copy --save-dev

Una vez que ya tenemos instalado el plugin, desde la línea de comandos en el directorio en el que está nuestro archivo gruntfile.js (la raíz del proyecto), ejecutamos grunt. Esto llevará a cabo la tarea que hemos creado. La primera vez que ejecutamos grunt, tendremos que añadir manualmente los archivos que se han añadido a la tarea al proyecto, aunque esta acción no tendremos que volver a hacerla.

Como hemos visto, hasta ahora estamos usando la línea de comando para ejecutar Grunt. Pero, ¿Y si queremos que se ejecute automáticamente Grunt cuando hacemos el deploy del proyecto, esto es posible?, la respuesta es si. Para esto tenemos varias extensiones de Visual Studio que nos lo permiten. En mi caso he utilizado Task Runner Explorer.

La descargamos y la instalamos y ya podremos usarla en nuestro Visual Studio 2013. Vamos a View->Other Windows->Task Runner Explorer y cuando se abre la nueva ventana, lo primero que hacemos es pulsar sobre el botón actualizar para que capture todas las tareas creadas si aún no lo habia hecho.

taskrunnerexplorer

Aquí vemos que podemos configurar si queremos que una determinada tarea se ejecute antes del deploy de la solución, con lo que podremos automatizar correctamente la ejecucion de Grunt y no tendremos que hacerlo manualmente a través de la línea de comandos.

run

binding

Os dejo para terminar, algunos enlaces más de utilidad relacionado con lo que hemos visto en la entrada:

http://gruntjs.com/
http://bower.io/
https://visualstudiogallery.msdn.microsoft.com/8e1b4368-4afb-467a-bc13-9650572db708

Y esto es todo por hoy, yo a partir de ahora voy a utilizar estas dos herramientas en todos mis proyectos, por las ventajas que ofrecen, espero que todos podáis hacerlo.

Un saludo y hasta la próxima

RoomReservation: Mi primera SharePoint Hosted App

Muy buenas a todos,

Llevaba algunos días sin publicar nada en el blog, y es que he estado algo ocupado. Durante los meses de enero y febrero he estado trabajando en conocer el nuevo modelo de desarrollo de SharePoint, en qué consiste el nuevo modelo de Apps y que son y como se usan las API Cliente. He escrito algunos artículos sobre estos temas, relacionados con la API REST, CSOM y cómo crear SharePoint Hosted Apps. Pero para poder profundizar más en su conocimiento decidí crear una aplicación de SharePoint que tuviera en cuenta todo lo que había estado aprendiendo.

Por esto surge principalmente RoomReservation. RoomReservation es una SharePoint Hosted App que nos proporciona un sistema muy sencillo de reserva de salas. La aplicación, consiste en un formulario y un calendario. El formulario nos permite rellenar los campos básicos para hacer una reserva de salas, nos muestra la fecha, las horas y las salas disponibles. La utilidad de la aplicación, es que a medida que vamos completando los datos de fecha y hora las salas disponibles se van actualizando, permitiéndonos solo reservar aquellas salas que realmente están libres en el horario que estamos solicitando.

roomreservationapplication

Esta es la interfaz de la aplicación. Para que funcione correctamente, necesita dos lista, una lista personalizada con los recursos o salas disponibles y otra lista de tipo calendario donde se almacenarán las reservas realizadas.

El modelo de desarrollo que he seguido para la App ha sido el siguiente:

  • Todo el acceso a datos de SharePoint se ha hecho a través de API REST.
  • Las listas que son necesarias, son provisionadas remotamente por la propia aplicación, si tras instalarla, no existían ya. Para ello se ha usado también API REST, la aplicación evalúa si existen las listas, si no, automáticamente te redirige a una página donde tienes la opción de crear las listas.

install

Todo el código de la aplicación está en GitHub en el siguiente enlace:

https://github.com/jcroav/room-reservation

A partir de ahora comenzaré a utilizar más a menudo GitHub para subir mis desarrollos, e incluso ejemplos y demos que vaya publicando en mi blog.

Os animo a todos a que ayudéis RoomReservation o propongáis aspectos de mejora, serán siempre bienvenidos. Yo también intentaré aprovechar todo lo que he aprendido y lo que vaya a aprender próximamente para seguir mejorando esta App y desarrollando algunas nuevas.

¿Cuáles son los aspectos de mejora?

Como por supuesto no creo que sea perfecta, ni mucho menos, tengo ya previstas algunas áreas de mejora. Próximamente espero seguir trabajando en mejorar la interfaz gráfica, hacerla más adaptativa, añadir alguna funcionalidad extra y trabajar en los idiomas de la App, ya que ahora mismo solo está en inglés.

Pues nada más de momento, lo dicho, espero que os sirva como siempre, y que lo encontréis útil.

Un saludo y buena semana.

Usando Promises con JQuery y API REST de SharePoint

Hola de nuevo a todos,

Empezando a trabajar con Javascript de forma intensiva, me he topado con la necesidad de usar un concepto del que había oído hablar hace tiempo, pero que hasta ahora no me había hecho falta. Este concepto es lo que se conoce como Promesas o Promises.

Cuando estamos trabajando con varias APIs a través de peticiones asíncronas o con una API pero realizamos varias peticiones a la vez, al final te puedes encontrar con el problema de que, peticiones que necesitas que se ejecuten después que otras lo están haciendo antes, por la propia naturaleza de las llamadas asíncronas. Puede ocurrir también que, funciones que necesitas que se ejecuten al finalizar las llamadas asíncronas lo estén haciendo antes. Para evitar esto puedes pensar en un primer momento en una montón de llamadas asíncronas anidadas en el callback de éxito de las llamadas asíncronas o por el contrario, usar las promesas.

¿Que son las promesas?

Para explicar lo que son las promesas, voy a tomar prestado la explicación de la siguiente entrada de un blog que he encontrado:

http://www.funcion13.com/2012/03/26/comprendiendo-promesas-y-deferreds-en-jquery/

La promesa (promise) en sí, es un objeto que representa un evento en el tiempo, como la llegada de datos de una petición asíncrona o el fin del procesamiento de todos los datos obtenidos en una función asíncrona. Un objeto promise es una versión de solo lectura de un objeto deferred.

Los deferred comienzan en estado pendiente, y es lógico si lo pensáis, pudiendo pasar a estado resuelto (la tarea se completó) o rechazado (si es que falló). En el momento en que un deferred cambie su estado a resuelto o rechazado no podrá cambiar nunca más.

Vamos a usar las promesas para manejar peticiones asíncronas, y controlar que se reciba una respuesta sea correcta o no. Además, con JQuery, podremos controlar, la respuesta de varias peticiones asíncronas por medio de promesas que se ejecutan simultáneamente. El código javascript, además, quedará mucho más ordenado.

Os dejo algunos enlaces que he usado yo para entender bien el concepto de promises.

http://www.danieldemmel.me/blog/2013/03/22/an-introduction-to-jquery-deferred-slash-promise/

http://joseoncode.com/2011/09/26/a-walkthrough-jquery-deferred-and-promise/

http://api.jquery.com/deferred.promise/

¿Cómo se usan las promesas?

En el ejemplo que os voy a mostrar a continuación, he usado las promesas para trabajar con la API REST de SharePoint. En mi caso, en una aplicación que estoy desarrollando, necesitaba hacer varias peticiones API REST para saber si varias listas existían para, en función de si lo hacían o no indicarlo al usuario para darle la oportunidad de crearlas.

El problema me surgía por la dificultad de controlar el resultado de las peticiones, ya que al ser asíncronas, las evaluaciones de su resultado se hacían antes de que la petición se hubiera realizado realmente, por lo que no funcionaba como quería que lo hiciera.

Para ello he usado el concepto de promesas, por medio de ellas, puedo controlar que todas las peticiones se hagan y den un resultado, y esperar hasta que se obtenga la respuesta de todas para llevar la acción que corresponda. Voy a mostraros el código y posteriormente explicarlo, en mi caso he usado la librería JQuery y el objeto Deferred de la misma:

function ExistList(listname) {

        var deferred = $.Deferred();

        var appweburl = GetQueryStringParameter("SPAppWebUrl");
        var hostweburl = GetQueryStringParameter("SPHostUrl");

        var executor = new SP.RequestExecutor(appweburl);

        executor.executeAsync({
            method: "GET",
            url: appweburl + "/_api/SP.AppContextSite(@target)/web/Lists/GetByTitle('" + listname + "')?@target='" + hostweburl + "'",
            headers: { "Accept": "application/json; odata=verbose" },
            success: function (data) {
                deferred.resolve();
            },
            error: function (data) {
                console.log(data);
                deferred.reject();
            }
        });

        return deferred.promise();
    }

function Validation(){

            var list1 = "List1";
            var list2 = "List2";

            $.when(ExistList(list1), ExistList(list2))
             .done(function () {

             })
             .fail(function () {
                 RedirectApp("Install.aspx");
             });
                
        }
  • El primer aspecto importante se encuentra en la función ExistList y es el objeto deferred, se crea al inicio de la función y al final de la función se devuelve la promesa. Dentro de la función se hace la petición asíncrona y en el callback de éxito y de error de la misma llamada, se indica si la promesa se ha cumplido (por medio de deferred.resolve()) o no (por medio de deferred.reject()) respectivamente.
  • En la función de validación el primer elemento importante es la llamada $.when() a la que le indicaremos como parámetro (tal y como podemos ver), todas las promesas que queremos que espere a obtener resultado. Los callback no se ejecutarán hasta que no se haya recibido la respuesta de todas las promesas indicadas como parámetro de la llamada.
  • En el caso de que todas las promesas hayan respondido positivamente se utiliza el callback de la llamada done.
  • En el caso de que alguna de las promesas haya respondido negativamente se ejecuta el callbak de la llamada fail

En ocasiones también podremos encontrarnos que se usa la llamada then que lleva como parámetros los dos callback de éxito y error, pero a mi me ha resultado más claro usar la llamada done y fail.

Cuando trabajamos con JQuery y usamos la llamada $.ajax(), ya de por sí devuelve una promesa por lo que no debemos encapsularla en una función para crear el objeto deferred.

Y con esto espero que si alguien no sabía o no había trabajado habitualmente con promesas, encuentre aquí como trabajar con ellas. Aunque el trabajo con promesas no es una novedad, hasta ahora no había necesitado trabajar con ellas, me ha resultado muy fácil, además de que el código queda muchísimo más limpio y organizado.

Un saludo a todos.