[SharePoint 2007] Camino de la Web 2.0 (Ajax Control Toolkit + Rating)

No se sí el título es muy acertado pero no se  me ocurría otra cosa :).

En un portal de publicación estamos añadiendo la parte de Rating, Comments, Cloud Tags, Geolocalización de información… (Web 2.0) y me he decantado por usar para la parte de rating el Ajax Control Toolkit de Microsoft (A partir de ahora ACT) y en concreto el control de Rating que incorpora. Os voy a comentar un poco las peripecias de hacer funcionar todo esto en SharePoint 2007:

Para poder usar el ACT es necesario añadir la librería a la GAC y añadir las siguientes entradas al Web.Config:

  • En el elemento SafeControls:
<SafeControl 

    Assembly="AjaxControlToolkit, Version=3.0.30930.22557, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" 

    Namespace="AjaxControlToolkit" 

    TypeName="*" 

    Safe="True" />

  • En el elemento assemblies:
<add 

    assembly="AjaxControlToolkit, Version=3.0.30930.22557, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e"/>

  • En elemento pages –> controls:
<add 

    namespace="AjaxControlToolkit" 

    assembly="AjaxControlToolkit, Version=3.0.30930.22557, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" 

    tagPrefix="ajaxToolkit"/>

 

 

Una vez hecho esto, vamos a añadir el ScriptManager a la página maestra de nuestro sitio, para ello lo primero añadir esta directiva de página:

<%@ Register 

    TagPrefix="ajaxToolkit" 

    Assembly="AjaxControlToolkit, Version=3.0.30930.28755, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" 

    Namespace="AjaxControlToolkit" %>

Por último añadir el ScriptManager a la página (En mi caso lo he añadido justo después del tag form):

<form id="Form1" runat="server" onsubmit="return _spFormOnSubmitWrapper();">

    <ajaxToolkit:ToolkitScriptManager runat="Server" EnablePartialRendering="true" ID="scriptManager" />

Con esto ya podemos trabajar con esta suite de controles, pero no todo es tan bonito y es que me he encontrado con varios problemas a la hora de trabajar con el control:

1) Cuidado con añadir la propiedad AutoPostback al control

Deja inutilizado el javascript del portal y cambia hasta el título de la página (Un comportamiento raro:S) además sí lo dejamos por defecto el evento Changed se ejecuta sin problemas.

image

Yo lo tengo así en el ascx:

<%@ Register 

    TagPrefix="ajaxToolkit" 

    Assembly="AjaxControlToolkit, Version=3.0.30930.28755, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" 

    Namespace="AjaxControlToolkit" %>

 

<asp:UpdatePanel runat="server" ID="updatePanel">

    <ContentTemplate>

        <ajaxToolkit:Rating 

            ID="rating" 

            runat="server"

            BehaviorID="RatingBehavior1"

            CurrentRating="0"

            MaxRating="5"

            RatingAlign="Horizontal"

            RatingDirection="LeftToRightTopToBottom"

            StarCssClass="ratingStar"

            WaitingStarCssClass="savedRatingStar"

            FilledStarCssClass="filledRatingStar"

            EmptyStarCssClass="emptyRatingStar"

            OnChanged="UpdateRating"

            style="float: left;" />

    </ContentTemplate>

</asp:UpdatePanel>

2) Bloquear el Rating para que sólo haya una valoración por usuario

Cuando un usuario valora un contenido, lo suyo es deshabilitar el control para evitar que valore más de una vez sobre el mismo contenido. Para ello, el control dispone de una propiedad pública llamada ReadOnly que al ponera a true no podrá modificarse. Sí lo haces a través de del evento OnChanged no te va a funcionar, es decir:

/// <summary>

/// Run custom code when the user rates something and then return a custom string

/// to the JavaScript client

/// </summary>

/// <param name="sender">Rating control</param>

/// <param name="e">RatingEventArgs</param>

protected void Rating_Changed(object sender, RatingEventArgs e)

{

    Rating.ReadOnly = true;

    e.CallbackResult = "Update done. Value = " + e.Value + " Tag = " + e.Tag;

}

 

Para ello, necesitamos hacer uso de JavaScript. Con el siguiente código podrás detectar el CallBack a cliente y deshabilitar el control:

javascript = @"Sys.Application.add_load(function() ;

                {

                    $find('RatingBehavior1').add_EndClientCallback( 

                        function(sender, e) 

                        {

                            $find('RatingBehavior1').set_ReadOnly(true);

                        });

                });"

 

Page.ClientScript.RegisterStartupScript(this.GetType(), "readonly", javascript, true);

 

Lo suyo es chequear en la parte servidora, que sí se hace el rating correctamente del tipo de contenido mandemos un OK al cliente para poder chequearlo en el CallBack y así deshabilitar o no el control.

3) Problemas al hacer click sobre la valoración que está marcada (CurrentRating)

Sí el usuario entra a ver el contenido de la publicación, le mostramos la media de la valoración de todos los usuarios (Por ejemplo 2 estrellitas) sí el quiere valorarlo con 2 estrellas no funcionará, es un error del Rating y he encontrado aquí este script para solucionarlo:

function AddNewRatingHandler() {

 

    AjaxControlToolkit.RatingBehavior.prototype._onStarClick =

        function(e) {

            if (this._readOnly) {

                return;

            }

            //   if (this._ratingValue != this._currentRating) {                    

            this.set_Rating(this._currentRating);

            // }

        };

        AjaxControlToolkit.RatingBehavior.prototype.set_Rating = function(value) {                    

            //   if (this._ratingValue != value) {

            this._ratingValue = value;

            this._currentRating = value;

            if (this.get_isInitialized()) {

                if ((value < 0) || (value > this._maxRatingValue)) {

                    return;

                }

 

                this._update();

 

                AjaxControlToolkit.RatingBehavior.callBaseMethod(this, 'set_ClientState', [this._ratingValue]);

                this.raisePropertyChanged('Rating');

                this.raiseRated(this._currentRating);

                this._waitingMode(true);

 

                var args = this._currentRating + ';' + this._tag;

                var id = this._callbackID;

 

                if (this._autoPostBack) {

                    __doPostBack(id, args);

                }

                else {

                    WebForm_DoCallback(id, args, this._receiveServerData, this, this._onError, true)

                }

 

            }

            //  }

        };                

}

AddNewRatingHandler();

 

 

 

Con todo esto y algo más ya puedo hacer Rating de todos los tipos de contenido del portal:

image

Por suerte todo esto lo tendremos disponible con SharePoint 2010 😉

Un saludo y hasta después de Reyes que me hace falta un descanso :)

[Tips] Modificaciones/Customizaciones no intrusivas en bases de datos

Una de las cosas que me gustó cuando empecé a trabajar con Microsoft Dynamics CRM era la manera en la que se reflejan las customizaciones sobre las entidades. Por ejemplo, la entidad Incidencia tiene su correspondiente tabla dbo.IncidentBase (Con los campos base de dicha entidad) y sí añadimos nuevos campos a esta entidad, no se añaden a esa tabla, sino que lo hace sobre otra llamada dbo.IncidentExtensionBase y así con todas las entidades que pueden ser personalizas. Esto desde mi punto de vista es una muy buena práctica y lo vamos a ver en el siguiente ejemplo:

Imagina que necesitas extender el MembershipProvider de ASP.NET porque necesitas añadir nuevos campos a la tabla de usuarios que te está pidiendo el departamento de HR

¿Cual puede ser el problema de añadir dichos campos a la tabla aspnet_Users?

Pues que sí le da a Microsoft por sacar un Service Pack o realizar modificaciones en nueva versión de NET y hace algo sobre estas tablas, puede ser que nuestros cambios no esten soportados o directamente que lo perdamos, esto se puede dar con otro tipo de base de datos siempre que lo permita el producto y sean modificacione soportadas que no nos hagan perder el soporte técnico.

Además de esta manera tenemos mejor controladas nuestras customizaciones sobre dicha base de datos.

Una buena solución es lo que hace CRM, no tocamos las tabla bases y añadimos nuestra extensión con FK a la tabla de aplicaciones y usuarios:

image

image

¿Qué os parece?

Un saludo y feliz navidad 😉

[Bing Maps] Poner un pushpin al resultado del método Find (where)

Desarrollando un callejero para un portal de SharePoint, me he encontrado la necesidad de añadir un pushpin al resultado del método Find que por defecto no lo hace. Es una tarea sencilla pero en mi caso he tenido que estar mirando la documentación y así lo dejo escrito, que tengo mala memoria y seguro que echo mano de ello alguna vez más :)

Si nos fijamos, el último parámetro del método Find, podemos ver que es una función:

callback : Nombre de la función a la que el servidor llama con los resultados de la búsqueda

¿Cual es la firma de esa función?

function (layer, whatResults, whereResults, hasMore)

En este caso particular, no detenedremos en el parámetro whereResults (También puedes usar whatResults para cuando buscas sitios tales como bares, restaurantes…), que es de tipo VEFindResult, donde podremos acceder a su propiedad LatLong que nos devolverá las coordenadas necesarias para crear el pushpin:

map.AddShape(new VEShape(VEShapeType.Pushpin, whereResults[0].LatLong));

 

A continuación os muestro una imagen y el código completo del ejemplo:

image

Código completo:

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

<html>

   <head>

      <title></title>

      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

 

      <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script>

      <script type="text/javascript">

        var map = null;

        var index = 0;

        var results = null;

 

        function GetMap()

        {

            map = new VEMap('myMap');

            map.LoadMap();

        }

 

        function FindLocation()

        {

            map.Clear();

            

            try

            {

               results = map.Find(null,

                                  document.getElementById('txtWhere').value,

                                  null,

                                  null,

                                  null,

                                  null,

                                  true,

                                  true,

                                  true,

                                  true,

                                  onSearchCompleted);

            }

            catch(e)

            {

               alert(e.message);

            }

        }

         

        function onSearchCompleted(layer, whatResults, whereResults, hasMore)

        {

            if(whereResults != null)

            {

                map.AddShape(new VEShape(VEShapeType.Pushpin, whereResults[0].LatLong));

            }

        }

      </script>

</head>

<body onload="GetMap();" style="font-family:Arial">

   <div id='myMap' style="position:relative; width:400px; height:400px;"></div>

   Where:

   <input id="txtWhere" type="text" name="txtWhere" value="Toledo, OH"/>

   <input id="find" type="button" value="Find" name="find" onclick="FindLocation();"/>

</body>

</html>

Un saludo

[MOSS 2007] Cannot retrieve properties at this time + Are you sure you want to navigate away from this page? = Headache

Sí alguna vez te has encontrado con estos 2 mensajes o con alguno de ellos te voy a intentar facilitar la vida un poco contandote mi experiencia:

Seguro que has buscado por internet y has encontrado soluciones para todos los gustos, pues a mí ninguna de ellas me ha funcionado (Para el primer mensajito), aunque si es verdad que alguna me ha dado alguna pista para encontrar la solución :)

Cannot retrieve properties at this time

Al pulsar sobre el botón de “Editor de texto enriquecido”

image

Aparece el dichoso mensaje:

image

Googleando he visto que aparecen distintas soluciones:

  1. Reciclar el pool
  2. Modificar web.config http://blogs.msdn.com/gyorgyh/archive/2009/03/04/troubleshooting-web-part-property-load-errors.aspx

El caso es que la primera (Por lo menos a mí no me ha funcionado) y la segunda de lo que comenta a mí no me aparecen esos valores en los web.config.

Así que con las Developer Tools he revisado la llamada que se estaba haciendo al servicio web que recupera dichas propiedades (como explicaba en uno de los artículos) para revisar el resultado de la llamada al servicio web, y como se aprecia en la imagen no hay respuesta:

image

Revisando el Web.config me he encontrado esto en el elemento httpHandlers:

<httpHandlers>

  <remove verb="GET,HEAD,POST" path="*" />

  <add verb="GET,HEAD,POST" path="*" ...

  <add verb="OPTIONS,PROPFIND,PUT,LOCK,...

  <add verb="*" path="Reserved.Rep...

  <add verb="*" path="*.asmx" ...

  <add verb="*" path="*_AppService.axd"...

  <add verb="GET,HEAD" ...

  <remove verb="*" path="*.asmx" />

</httpHandlers>

Solución: Pues que aunque se están habilitando todos los verbos para los archivos con extensión *.asmx (Servicios Web) al final se están quitando, por lo que quitando el ultimo remove:

image

Ya tenemos respuesta del servicio web y no saldrá la dichosa ventana, perooooooo… ahora nos aparece el siguiente mensaje

Are you sure you want to navigate away from this page?

image

Para esto sí que he encontrado la solución Googleando, que es deshabilitar el soporte nativo de XMLHTTP o lo que es lo mismo deshabilitar el check “Enable native XMLHTTP support”:

image

Un saludo

[Tips] Access Denied en la GAC y como resolverlo a mi manera

Un pequeño truquillo, que aunque puede paracer una tontería (Me ha llevado un rato) pero esta mañana desplegando una dlls en un servidor W2k8 R2 no podía ni instalar, desinstalar, ni drag & drop desde una carpeta a la GAC, puesto que me aparecía el dichoso mensaje:

Failure adding assembly to the cache: Access denied :S

En W2k3, se soluciona haciendo un restart del Servicio de Index Server (Por lo menos en mi caso) pero en W2k8 no viene instalado por defecto y revisando con mi compañero Chan (FicheroPillao.exe XD) quién tenía esa dll pillada, no aparecía ningún proceso.

He leido por internet que puede ser un tema de permisos (Por no usar un Administrador local de máquina) y también he leido que había que ejecutar el comando:

CACLS %WINDIR%assembly /e /t /p [DOMAIN|MACHINENAME]useraccount:R

pero tampoco me funcionaba, como tampoco tenía el gacutil para probar, lo he solucionado con XCOPY origen destino.

A ver sí os sirve para no perder el tiempo.

Un saludo

PD: Con el comando XCOPY me he acordado de mi amigo David Hurtado :)