SharePoint, jQuery y la Experiencia de Usuario

jQuery es un  framework de Javascript que nos permite realizar ciertas maldades en SharePoint para mejorar la experiencia del usuario.

jquery

Una de las grandes ventajas de SharePoint es que es una aplicación extensible. Podemos crear o editar los formularios estándares de las listas, podemos crear Web Parts de usuarios, nuevos tipos de datos y definir cual es la interfaz html que implementa el tipo, etc.

Toda esta potencia en forma de extensibilidad no es nada si no le aplicamos una experiencia de usuario buena, y para esto podemos utilizar jQuery.

Por ejemplo, SharePoint nos permite definir campos de búsquedas que se renderizan como Combos y permiten al usuario seleccionar un elemento de una lista externa. Cuando tenemos más de 10 elementos a buscar, la usabilidad de ese Combo deja mucho que desear,  ¿por qué no añadir un elemento al formulario que nos permite buscar ese elemento y en el caso que se encuentren más de un resultado, que le de la elección al usuario, en un Modal Popup, con la lista de elementos encontrados?.

sharepoint_jquery_1

Cuando el usuario escribe un término de búsqueda y pulsa enter o hace click en la imagen, si el resultado no es único, obtiene un Modal Popup con el resultado de la búsqueda para que seleccione el elemento que estaba buscando.

sharepoint_jquery_2

Una vez seleccionado y para mejorar aún su experiencia, se le incluye un resumen a la derecha del formulario de alta.

sharepoint_jquery_3

Estas mejoras se han desarrollado modificando el formulario de alta NewForm.aspx inyectando código con jQuery. Se han utilizado varias librerías, entre las que se encuentra una librería que nos permite consultar elementos de listas de SharePoint utilizando jQuery y los servicios web de este (SPServices).

   1: <script language="javascript" type="text/javascript" src="/jquery/jquery-1.4.2.min.js"></script>

   2: <script language="javascript" type="text/javascript" src="/jQuery/jquery.SPServices-0.5.2.min.js" ></script>

   3: <script language="javascript" type="text/javascript" src="/jQuery/jquery-ui.js"></script>

   4: <script language="javascript" type="text/javascript" src="/jQuery/Load.js"></script>

Cuando se carga el formulario se inyecta el label, input y button para la búsqueda.

   1: $(document).ready(function() {

   2:  

   3:     var trow = $("<tr>");

   4:  

   5:     $("<h3>").addClass("ms-standardheader")

   6:         .text("Buscar")

   7:         .appendTo(

   8:             $("<td>").addClass("ms-formlabel")

   9:                 .css("width", "190")

  10:                 .attr("noWrap", "nowrap")

  11:                 .attr("vAlign", "top")

  12:                 .appendTo(trow)

  13:         );

  14:     $("<td>").addClass("ms-formbody")

  15:         .html("<div style='float:right; margin:2px 10px 0 0;'><img id='btSearch' src='_layouts/images/checkitems.gif' alt='Comprobar' title='Comprobar' style='cursor:pointer;' /></div>' +

  16:             "<span dir="none"><input id="tfBuscar" name="tfBuscar" title="Buscar" class="ms-long" type="text" maxLength="255" style='width:365px' /></span>' +

  17:             "<div id='progressSearch' style='display: none;'>Buscando...</div>')

  18:         .css("width", "400")

  19:         .attr("vAlign", "top")

  20:         .appendTo(trow);

  21:  

  22:     $($(".ms-formtable").children("tbody").children("tr")[0]).before(trow);

  23:  

  24:     $("#btSearch").click(function() {

  25:         clickImageButtonService("Buscar");

  26:     });

  27:     $("#tfBuscar").focus().keypress(function(event) {

  28:         if ((event.keyCode && event.keyCode == '13') || (event.which && event.which == '13')) {

  29:             event.preventDefault();

  30:             clickImageButtonService("Buscar");

  31:         }

  32:     });

La función de búsqueda utiliza AJAX para realizar la consulta a un servicio WCF JSON externo (obtiene los datos un sistema de información externo) y los muestra. Si el resultado de la búsqueda es mayor que uno, se muestra el Modal Popup.

   1: function clickImageButtonService(displayName) {

   2:     indicatorID = '#progressSearch'; 

   3:     var valueQuery = $("input:[title='" + displayName + "']").val();

   4:     var params = { "search": valueQuery };

   5:     var urlService =  "http://server/test/service.svc/json/Search";

   6:  

   7:     if (valueQuery != "" && valueQuery != "undefined") {

   8:         var incObj = $("#incResult");

   9:         if (incObj != null && incObj != undefined)

  10:             incObj.remove();    

  11:             

  12:         $.ajax({

  13:             type: "GET",

  14:             url: urlService,

  15:             data: params,

  16:             contentType: "application/json; charset=utf-8",

  17:             dataType: "json",

  18:             success: successFunction,

  19:             error: function(xhr, err) {

  20:                 alert("Se ha producido un error al realizar la búsqueda.rnrnEstado del proceso: " + 

  21:                 xhr.readyState + "rnCódigo de estado: " + xhr.status +

  22:                 "rnMensaje de respuesta: " + xhr.responseText); 

  23:             }

  24:         });

  25:     }

  26: }

Esta es la función que se ejecuta cuando la llamada asíncrona al servicio es satisfactoria.

   1: function successFunction(msg) {

   2:     if (msg != null) {

   3:         if (msg.d.length == 0) {

   4:             initFields();

   5:             var divObj = $("#errorMesage");

   6:             if (divObj.html() != null && divObj != undefined) {

   7:                 divObj.css("display","block");

   8:             }

   9:             else {

  10:                 $("#tfBuscar").parent("span")

  11:                     .append("<div id='errorMesage' class='ms-error'>No sé encontró ningún elemento. Realice una nueva búsqueda.</div>");

  12:             }

  13:         }

  14:         else {

  15:             var divObj = $("#errorMesage");

  16:             if (divObj != null && divObj != undefined) {

  17:                 divObj.css("display","none");

  18:             }

  19:             if (msg.d.length > 1) {

  20:                 createDynamicTableServices(msg.d);

  21:                 $("#modalPopUp td img")

  22:                     .click(function() {

  23:                         fillFieldsFromTable($(this).parent("td").parent("tr"));

  24:                         $("#modalPopUp").dialog('close');

  25:                     });

  26:                 $("#modalPopUp").dialog({ resizable: false, draggable: false, height: 400, width: 600, modal: true, closeOnEscape: false });

  27:             }

  28:             else {

  29:                     fillFieldsFromDataServices(msg.d);

  30:             }

  31:         }

  32:     }

  33: }

Para mostrar el resumen del elemento encontrado en la búsqueda, se realiza una llamada al método GetListItems del servicio web de lista de SharePoint (Lists.asmx). SPServices nos permite utilizar los servicios de SharePoint pero como si fueran funciones jQuery, tan solo tenemos que construir el CAML con la consulta y los campos que queremos recibir de la lista.

   1: function searchIncidencias(numero) {

   2:  

   3:     var queryString = "<Query><Where><And>" +

   4:         "<Contains><FieldRef Name='Numero' /><Value Type='Text'>" + numero + "</Value></Contains>" +

   5:         "<Or><Eq><FieldRef Name='Estado' /><Value Type='Lookup'>Iniciada</Value></Eq>" +

   6:         "<Eq><FieldRef Name='Estado' /><Value Type='Lookup'>En curso</Value></Eq></Or>" +

   7:         "</And></Where></Query>";

   8:  

   9:     var viewFields = "<ViewFields><FieldRef Name="ID"></FieldRef><FieldRef Name="Title"></FieldRef><FieldRef Name="Serie"></FieldRef>" +

  10:             "<FieldRef Name="Estado"></FieldRef><FieldRef Name="Fecha_x0020_Alta"></FieldRef></ViewFields>";

  11:     

  12:     $().SPServices({

  13:         operation: "GetListItems",

  14:         listName: "Solicitudes",

  15:         async: false,

  16:         CAMLViewFields: viewFields,

  17:         CAMLQuery: queryString, 

  18:         completefunc: completeSearchIncidencias

  19:     });

  20: }

  21:  

  22: function completeSearchIncidencias(xData, Status) {

  23:     var tdHtml = "<td valign="top"><div id="incResult" style='margin-left: 5px; margin-top: 29px;' > </div></td>';

  24:     $($("#onetIDListForm").children("tbody").children("tr")[0]).append(tdHtml);

  25:     var tbody = $("<table>").attr("cellspacing", "0").attr("cellpadding", "0");

  26:     var trow = $("<tr>");

  27:     $("<td>").addClass("ms-toolbarContainer")

  28:             .html("<span class="ms-toolbar" style='padding: 2px 2px 2px 2px; height = 18px;'>Incidencias activas</span>')

  29:             .attr("colspan", "2").appendTo(trow);

  30:     trow.appendTo(tbody);

  31:     $("<td>").html("<img height="5" width="1" alt="" src="/_layouts/images/blank.gif">").attr("colspan", "2").appendTo($("<tr>").appendTo(tbody));

  32:  

  33:     if ($(xData.responseXML).find("z\:row").length > 0) {

  34:         $(xData.responseXML).find("z\:row").each(function() {

  35:             trow = $("<tr>");

  36:             var incidencia = getEnlaceIncidencia($(this).attr("ows_ID"), $(this).attr("ows_Title"));

  37:             $("<td>").addClass("ms-formlabel").html("<h3 class="ms-standardheader">Número</h3>").appendTo(trow);

  38:             $("<td>").addClass("ms-formbody").html(incidencia).appendTo(trow);

  39:             trow.appendTo(tbody);

  40:             trow = $("<tr>");

  41:             $("<td>").addClass("ms-formlabel").html("<h3 class="ms-standardheader">Número serie</h3>").appendTo(trow);

  42:             $("<td>").addClass("ms-formbody").text($(this).attr("ows_N_x00fa_mero_x0020_Serie")).appendTo(trow);

  43:             trow.appendTo(tbody);

  44:             trow = $("<tr>");

  45:             $("<td>").addClass("ms-formlabel").html("<h3 class="ms-standardheader">Fecha Alta</h3>").appendTo(trow);

  46:             var fecha = $(this).attr("ows_Fecha_x0020_Alta");

  47:             formatDate(fecha);

  48:             $("<td>").addClass("ms-formbody").text(formatDate(fecha)).appendTo(trow);

  49:             trow.appendTo(tbody);

  50:             trow = $("<tr>");

  51:             $("<td>").addClass("ms-formlabel").html("<h3 class="ms-standardheader">Estado</h3>").appendTo(trow);

  52:             $("<td>").addClass("ms-formbody").text($(this).attr("ows_Estado").split("#")[1]).appendTo(trow);

  53:             trow.appendTo(tbody);

  54:             $("<td>").addClass("ms-formline").html("<img height="5" width="1" alt="" src="/_layouts/images/blank.gif">").attr("colspan", "2").appendTo($("<tr>").appendTo(tbody));

  55:         });

  56:     }

  57:     else {

  58:         trow = $("<tr>");

  59:         var incidencia = getEnlaceIncidencia($(this).attr("ows_ID"), $(this).attr("ows_Title"));

  60:         $("<td>").addClass("ms-formlabel").attr("colspan", "2").text("No encontradas incidencias activas").appendTo(trow);

  61:         trow.appendTo(tbody);

  62:         $("<td>").addClass("ms-formline").html("<img height="5" width="1" alt="" src="/_layouts/images/blank.gif">").attr("colspan", "2").appendTo($("<tr>").appendTo(tbody));

  63:     }

  64:     $("#incResult").append(tbody);

  65: }

  66:  

Gracias a jQuery hemos sido capaces de mejorar notablemente la experiencia del usuario y que nuestra lista de SharePoint sea más usable.

Aunque el mundo de jQuery, este código lo dice todo, es un poco singular, tengo que reconocer que la facilidad de realizar estas modificaciones me ha asombrado gratamente. Una vez que hemos realizado estos cambios, me he propuesto hacerle más seguimiento a jQuery y buscarle más utilidades en SharePoint.

 

Saludos a todos…

Operadores LINQ

LINQ (Language INtegrated Query) nos permite escribir consultas (parecidas a consultas SQL) sobre nuestras colecciones (List, IEnumerable, …). Con estos operadores estándares, podremos hacer consultas determinando si existe un tipo de elemento o realizar una suma de la secuencia. Básicamente nos permite filtrar, enumerar y crear proyecciones de varios tipos de colecciones.

En LINQ tenemos un amplio conjunto de operadores. La siguiente tabla muestra los operadores de LINQ y una breve descripción de la funcionalidad que ofrecen.

Operador

Descripción
Agregación  
Aggregate Realiza un método personalizado sobre una secuencia
Average Calcula el promedio de una secuencia de valores numéricos
Count Devuelve el número de los elementos de una secuencia como un int
LongCount Devuelve el número de los elementos de una secuencia como un largo
Min Encuentra el número mínimo de una secuencia de números
Max Encuentra el número máximo de una secuencia de números
Sum Suma de los números en una secuencia
   
Concatenación  
Concat Concatena dos secuencias en una secuencia
   
Conversión  
Cast Convierte elementos de una secuencia en otro tipo dado
OfType Filtra los elementos de una secuencia de un determinado tipo
ToArray Devuelve un Array de una secuencia
ToDictionary Devuelve un Dictionary de una secuencia
ToList Devuelve un List de una secuencia
ToLookup Devuelve un Lookup de una secuencia
ToSequence Devuelve un IEnumerable de una secuencia
   
Elemento  
DefaultIfEmpty Crea un elemento predeterminado para una secuencia vacía
ElementAt Devuelve el elemento de un índice determinado en una secuencia
ElementAtOrDefault Devuelve el elemento de un índice determinado en una secuencia o un valor predeterminado si el índice está fuera del intervalo
First Devuelve el primer elemento de una secuencia
FirstOrDefault Devuelve el primer elemento de una secuencia o un valor predeterminado si no se encuentra ningún elemento
Last Devuelve el último elemento de una secuencia
LastOrDefault Devuelve el último elemento de una secuencia o un valor predeterminado si no se encuentra ningún elemento
Single Devuelve el único elemento de una secuencia
SingleOrDefault Devuelve el único elemento de una secuencia o un valor predeterminado si no se encuentra ningún elemento
   
Igualdad  
SequenceEqual Compara dos secuencias para ver si son equivalentes
   
Generación  
Empty Genera una secuencia vacía
Range Genera una secuencia dado un rango
Repeat Genera una secuencia repitiendo un elemento un número determinado de veces
   
Agrupación  
GroupBy Agrupa elementos de una secuencia por una agrupación dada
   
Enlazando  
GroupJoin Realiza una combinación agrupada de dos secuencias
Join una combinación interna de dos secuencias
   
Ordenación  
OrderBy Ordena una secuencia por valor (s) en orden ascendente
OrderByDescending Ordena una secuencia por valor (s) en orden de descendente
ThenBy Ordena una secuencia que ya se ha ordenado en orden ascendente
ThenByDescending Ordena una secuencia que ya se ha ordenado en orden descendente
Reverse Invierte el orden de los elementos de una secuencia
   
Particionado  
Skip Devuelve una secuencia que omite un número determinado de elementos
SkipWhile Devuelve una secuencia que omite elementos que no cumplen una expresión
Take Devuelve una secuencia que toma un número determinado de elementos
TakeWhile Devuelve una secuencia que toma elementos que cumplen una expresión
   
Proyección  
Select Crea una proyección  a partir de una secuencia
SelectMany Crea una proyección One to Many de elementos de una secuencia
   
Cuantificadores  
All Determina si todos los elementos de una secuencia cumplen una condición
Any Determina si los elementos de una secuencia cumplen una condición
Contains Determina si una secuencia contiene un elemento determinado
   
Restricción  
Where Filtra los elementos de una secuencia
   
Conjuntos  
Distinct Devuelve una secuencia sin elementos duplicados
Except Devuelve una secuencia que representa la diferencia entre dos secuencias
Intersect Devuelve una secuencia que representa la intersección de dos secuencias
Union Devuelve una secuencia que representa la unión de dos secuencias

 Vamos a ver algunos ejemplos de código:

Average

   1:  int[] nums = new int[] {1,2,3,4,5,6,7,8,9};
   2:  double avg = nums.Average();
Calculamos la media de los enteros del array.
 

Where

   1:  IEnumerable<Customer> customersInTenerife = customers.Where(c => c.City == "Tenerife");

Buscamos los clientes cuya ciudad sea Tenerife.

 

Take

   1:  IEnumerable<Product> MostExpensive10 =
   2:      products.OrderByDescending(p => p.UnitPrice).Take(10);
En este ejemplo, ordenamos los productos descendentemente por UnitPrice y luego 
seleccionamos 10 productos.

 

Skip

   1:  IEnumerable<Product> AllButMostExpensive10 =
   2:      products.OrderByDescending(p => p.UnitPrice).Skip(10);

 

Aquí ordenamos, de nuevo, descendentemente por UnitPrice y seleccionamos todos los elementos menos los 10 más caros.

 

Join

   1:  var custOrders =
   2:      customers.Join(orders, c => c.CustomerID, o => o.CustomerID,
   3:                               (c, o) => new { c.Name, o.OrderDate, o.Total }
   4:                                );

 

Enlazamos los clientes con sus facturas y devolvemos una lista con el nombre del cliente, la fecha de la factura y el total facturado.

 

OrderBy/ThenBy

   1:  IEnumerable<Product> orderedProducts =
   2:      products.OrderBy(p => p.Category).
   3:                     ThenByDescending(p => p.UnitPrice).
   4:                     ThenBy(p => p.Name);

 

La siguiente consulta ordena la lista de productos por Category, luego por UnitPrice descendiente y por último por Name.

 

GroupBy

   1:  IEnumerable<IGrouping<string, Product>> productsByCategory =
   2:      products.GroupBy(p => p.Category);

 

Podemos agrupar los productos y obtener un IEnumerable agrupado que contenga la Category y los productos que corresponden a esa Category.

 

ToDictionary

   1:  Dictionary<int,Order> orders =
   2:      customers.SelectMany(c => c.Orders).
   3:                Where(o => o.OrderDate.Year == 2005).
   4:                ToDictionary(o => o.OrderID);

Creamos un Dictionary seleccionando las Facturas de los clientes del año 2005, utilizando como clave el OrderID.

 

Any

   1:  bool result = customers.Any(c => c.Name == "Pepe");

 

Devuelve True si encuentra algún cliente con el Name Pepe.

 

ForEach

   1:  List<customer> customers = GetCustomers();
   2:  customers.ForEach(p => Insertar(p));

Para cada cliente de la lista customers, ejecutamos el método Insertar pasándole por parámetro el elemento actual.

 

Si queremos más información sobre LINQ, lo mejor es pasarse por la MSDN y leernos la documentación o alguno de los tutoriales que tenemos sobre él.

Este es uno de los grandes avances que Microsoft incluyó con el Framework 3.5 y que nos facilita bastante la vida y nos permite programar mejor y más rápido.

Espero que disfrutéis tanto como yo escribiendo código y que lo hagáis más con LINQ, os lo recomiendo.

 

Saludos a todos…

¿EF vs NHibernate? ¿Esa es la pregunta?

El martes 16 estuve hablando de Entity Framework en el Webcast de SecondNug. La idea del Webcast era crear cultura de modelado de entidades sobre modelos relacionales y fomentar el uso de los ORM.

Una de las slides que presenté enseñaba una comparativa de uso entre Entity Framework y NHibernate.

efvsnh_1

Esta comparativa es del 2009 y ya han realizado una revisión de la misma, ya que los desarrolladores de NHibernate comentaron que no se realizó en igualdad de condiciones. Podemos consultar esta revisión en el siguiente post.

El objetivo de esta slide pasaba porque no le diéramos más importancia a un orm o a otro en función de su rendimiento, si analizamos los datos podemos extraer que EF es más potente en algunas cuestiones y que NHibernate lo es en otras. Aún así, siempre encontraremos algún método en el que utilizando algún tipo de artefacto que el otro no emplee se obtendrá un mejor rendimiento.

Creo que tanto NHibernate como Entity Framework son dos grandes ORM y que utilicemos uno u otro tendría que ser cuestión de evaluar las herramientas y diseñadores que tenemos para trabajar con ellos.

Igual que la gente de NHibernate comentó que el test no se realizó en igualdad de condiciones, los desarrolladores de Enfity Framework podrán decir que por qué no se realizaron los test con Entity Framework 4, que las consultas que utilizaron en el test no son las óptimas, etc.

Esta no es nuestra guerra, nuestra guerra pasa porque empecemos a utilizar un ORM en nuestras aplicaciones y que modelemos nuestros sistemas.

Me iba a poner a mejorar el código de test y actualizarlo a EF4, pero creo que no vale la pena seguir con estas comparativas. Mi elección va a ser Entity Framework 4 porque las herramientas que tenemos en Visual Studio 2010 son casi insuperables y porque la curva de aprendizaje es inferior a utilizar otro framework.

 

Saludos a todos…

Consolas SharePoint en x64

Ahora que se acerca la nueva versión de SharePoint 2010, empezaremos a prepara migraciones y scripts para pasar de una versión a otra, o incluso para obtener listados de sitios, usuarios, permisos, etc.

Para este tipo de cosas, solemos crearnos una aplicación de consola y así poder utilizar el modelo de objetos de SharePoint 2007 y realizar las consultas.

Si tenemos hecha la tarea y nuestra granja de SharePoint 2007 es una granja x64, cuidado con la siguiente excepción cuando ejecutemos estas aplicaciones de consola:

Unhandled Exception: System.IO.FileNotFoundException: The Web application at http://sp2007 could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application.

No nos pongamos a cambiar la URL de nuestro código (SPSite site = new SPSite(SITEURL)), este no es el problema. Seguro que conocemos perfectamente la URL de nuestra granja y la estamos escribiendo bien (hay que revisarla por si acaso).

El problema reside en el Platform target de nuestra aplicación de consola. Nuestra aplicación se está compilando para x86 y no para x64 con lo que no le es posible acceder al modelo de objetos de nuestro servidor que está en x64.

sharepointx64console_1

Sólo tenemos que cambiar el Platform taget to x64 o Any CPU y listo, compilamos nuestra aplicación y la podemos ejecutar sin problemas en uno de los servidores de la granaja x64.

 

Saludos a todos…

Widgets en Windows Mobile

En Windows Mobile 6.5 se pueden desarrollar aplicaciones basadas en el estándar W3C Widgets. Esto nos permite empaquetar aplicaciones HTML y JavaScript y que se ejecuten en nuestros dispositivos móviles.

Utilizando HTML y CSS proporcionamos la interfaz de usuario, y el código lo escribiremos en JavaScript. Los Widgets se instalan localmente en el dispositivo móvil y se ejecutan de forma local, pero pueden utilizar datos de la nube.

La pregunta es, ¿Por qué desarrollar un widget para un dispositivo móvil que obtiene datos de la nube, si sería los mismo, incluso más rápido, desarrollar una aplicación web a la que acceda el dispositivo cuando necesite estos datos?

Los widgets nos permiten ahorrar tráfico en el dispositivo. Cuando un dispositivo móvil accede a una aplicación web se tiene que descargar el html, las css, las imágenes, los archivos javascript, y, además de todo esto, obtener la información que le solicitamos. Si analizamos la arquitectura de un widget, este está pensado para que nuestro dispositivo se centre en obtener los datos y no en obtener la capa de presentación del mismo. Esto quiere decir, nuestros widgets consumen menos tráfico de red que las aplicaciones web, y esta es una característica de diseño clave para nuestros dispositivos móviles que están basado en las redes que nos cobran por la cantidad y la velocidad de descarga de paquetes. Si a todo esto le sumamos que existe un widget API que nos proporciona objetos JavaScript que nos permite integrar nuestras aplicaciones con el dispositivo (funciones de localización, estado del dispositivo, acceso a menús, etc.), encontramos dos grandes ventajas para desarrollar widgets y no aplicaciones web.

Contenido del Paquete

Un widget es un fichero Zip con el contenido de nuestra aplicación web. Páginas HTML, código JavaScript, hojas de estilos CSS, imágenes, etc.

Widget_1

Dentro de este zip, debemos de incluir un fichero config.xml, que es el Manifest del widget. Todos los elementos del config son opcionales, pero es recomendable incluirlos para conocer la información clave de nuestro widget.

   1: <?xml version="1.0" encoding="utf-8" ?>

   2: <widget xmlns="http://www.w3.org/ns/widgets"

   3:         version="1.0"

   4:         id="http://someSite.com/MyUniqueWidgetID">

   5:   <name>My first widget</name>

   6:   <content src="widget.htm" type="text/html" />

   7:   <access network="true" />

   8:   <icon src="icon.png"/>

   9:   <icon src="icon.ico"/>

  10:   <description>

  11:     This is my first widget,

  12:     it won't make a lot of money on the

  13:     marketplace but at least is cute!

  14:   </description>

  15:   <author href="http://www.myweb.com"

  16:           email="widget@myweb.com">Authos Name</author>

  17:   <license>

  18:     Example license

  19:   </license>

  20: </widget>

Los elemento del config son:

  • Version. Número de versión del widget, para poder realizar las actualizaciones utilizando el widget id.
  • Id. Este elemento es el que va a identificar a nuestro widget en el dispositivo. Tiene que ser una URI, pero no tiene que ser valida o accesible.
  • Name, Description, Author, License. Estos campos describen la información del widget basada en nombre, descripción de la funcionalidad, autor del mismo, licencia.
  • Content. Este elemento especifica que fichero se va a cargar cuando el widget se inicie.
  • Icon. Nos permite especificar el icono que va a mostrar nuesto widget en el menú de nuestro dispositivo móvil.
  • Access Network. Nos sirve para especificar si nuestro widget tendrá permisos para acceder a los recursos de la red.

Por su puesto, toda esta información es accesible para nuestra aplicación utilizando el widget API.

Programando el Paquete

Vamos a desarrollar un widget que, utilizando jQuery y el API de Twitter, nos muestre los mensajes de un usuario.

Widget_2

Esta es nuestra estructura, ahora explicaremos que es lo que tenemos en ella.

config.xml

Definimos nuestro fichero Manifest de la siguiente forma:

   1: <?xml version="1.0" encoding="utf-8" ?> 

   2: <widget version="1.0" 

   3:         xmlns="http://www.w3.org/ns/widgets" 

   4:         id="http://adiaz.ms/Twitter"> 

   5:   <name>Twitter Search</name> 

   6:   <content src="inicio.html" type="text/html" /> 

   7:   <access network="true" /> 

   8:   <icon src="img/icon.png"/> 

   9:   <description>Este widget realiza consultas de las publicaciones de un usuario de Twitter.</description> 

  10: </widget> 

Incluimos los siguiente ficheros: jquery.min.js y jquery.twitter.js. Estos dos ficheros JavaScript nos permiten realizar consultas al API de Twitter y mostrar los mensajes de un usuario.

Una vez que tenemos todos estos, incluyendo hojas de estilos y alguna que otra imagen, escribimos la página html que va a contener la funcionalidad de obtener los mensajes de un usuario de Twitter.

   1: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

   2: <html xmlns="http://www.w3.org/1999/xhtml">

   3:     <head>

   4:         <title>Search on Twitter</title>

   5:         <link rel="stylesheet" href="css/jquery.twitter.css" type="text/css" media="all" />

   6:         <script type="text/javascript" src="js/widget.js"></script>
   1:  

   2:         <script type="text/javascript" src="js/jquery.min.js">

   1: </script>

   2:         <script type="text/javascript" src="js/jquery.twitter.js">

   1: </script>

   2:         <script type="text/javascript" src="js/twitter.js">

   1: </script>

   2:         <script type="text/javascript">applyCssByDPI();

</script>

   7:     </head>

   8:  

   9:     <body onload="SetupPage()">

  10:         <form id="search_form" action="javascript:SearchTwitter();" >

  11:             <div id="content">

  12:                 <div class="sw_b1 sw_qbox" id="search_box">

  13:                     <table width="100%"><tr>

  14:                         <td id="search_box_cell">

  15:                             <input class="sw_qbox" id="q" name="q" title="Search for..." type="text" value="" maxlength="48" />

  16:                         </td>

  17:                         <td align="right">

  18:                             <input class="sw_qbtn" id="go" name="go" tabindex="0" title="Search" type="button" onclick="javascript:SearchTwitter();"/>

  19:                         </td>

  20:                     </tr></table>

  21:                     <input name="a" id="a" type="hidden" value="results" />

  22:                     <input name="mid" id="mid" type="hidden" value="1017" />

  23:                 </div>

  24:                 <div id="twitter"></div>

  25:             </div>

  26:         </form>

  27:     </body>

  28: </html>

 

 

En esta página se hace uso del API de widgets para dos funciones básicas, una para seleccionar el CSS en función del tamaño del dispositivo y otra para configurar el menú de nuestro widget para que nos salga el menú Buscar en las opciones del mismo.

Selección del CSS

 

   1: function applyCssByDPI() {

   2:     if (WidgetAPI.getDeviceDPI() == WidgetAPI.WIDGET_DEVICE_HIGH_DPI)

   3:         document.write('<link rel="stylesheet" type="text/css" href="css/twitter192.css" />');

   4:     else {

   5:         document.write('<link rel="stylesheet" type="text/css" href="css/twitter.css" />')

   6:     }

   7: }

Creación del Menú

   1: function SetupPage() {

   2:     setLeftSoftKey(SEARCH_MENU_ITEM_ID);

   3: }

   4:  

   5: var SEARCH_MENU_ITEM_ID = 1005;

   6:     

   7: function menuHandler(a) {

   8:     switch (a) {

   9:         case SEARCH_MENU_ITEM_ID:

  10:             document.getElementById("search_form").submit()

  11:     }

  12: }

  13:  

  14: function setLeftSoftKey(d, c) {

  15:     var a = null;

  16:     if (c)

  17:         a = c;

  18:     else

  19:         a = menuHandler;

  20:     var b = null;

  21:     switch (d) {

  22:         case SEARCH_MENU_ITEM_ID:

  23:             b = WidgetAPI.createMenuItem(SEARCH_MENU_ITEM_ID, "Buscar", a);

  24:             break;

  25:     }

  26:     if (b)

  27:         WidgetAPI.setLeftSoftKey(b)

  28: }

 

Y con todo esto nos queda empaquetar en un Zip nuestros ficheros, cambiarle la extensión por .widget, copiarlo a nuestro dispositivo móvil, instalarlo y ejecutarlo para ver que es capaz de obtener los mensajes de un usuario de Twitter, utilizando Ajax.

Podemos ver los widgets instalados en la carpeta Dispositivo Móvil/Archivos de Programa/Widgets/User/ID (este Id es el que el dispositivo móvil le pone cuando se instala).

Widget_3

En la MSDN hay un muy buen artículo explicando en detalle como Desarrollar Widgets para Windows Mobile 6.5.

A ver si consigo otros móviles (Android, BlackBerry, etc) y escribo un artículo con los cambios que tendríamos que realizar para conseguir la multiplataforma de nuestro widgets.

Os dejo en enlace al widget de ejemplo que he utilizado en el artículo.

 

Saludos a todos…

[SecondNug] Webcast Modelando entidades, Entity Framework 4

El próximo martes 16 de Marzo a las 18.30 (GMT) me toca estrenarme en SecondNug con un evento en el que intentaré enseñaros a pensar como el nuevo Entity Framework 4.0.

banner_EF4

Hablaré de las nuevas capacidades de la versión 4 para poder utilizarlo en múltiples enfoques de desarrollo, por ejemplo, el uso de code-first, model-first y database-first. Todo esto, manteniendo independiente nuestra Interfaz de usuario (WPF, Silverlight, WCF, ASP.NET, etc.) del modelo de acceso a datos. Intentaré explicaros las ventajas y desventajas de trabajar con Entity Framework y veremos las novedades que nos vienen con esta nueva versión.

Os espero a todos!

Enlace de registro del evento: http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032444597&EventCategory=4&culture=es-AR&CountryCode=AR

No olvidéis tener instalado Microsoft Office Live Meeting (lo podemos descargar en el siguiente enlace).