Código Apex y Visualforce de FindNearby

Éste es el código que usaremos para el ejemplo de mostrar las cuentas cercanas a nosotros y que estén a menos de 1 Km de distancia.

Clase Apex FindNearby:

global with sharing class FindNearby {
    public FindNearby(ApexPages.StandardController controller) {}

    @RemoteAction
    // Find Accounts nearest a geolocation
    global static List<Account> getNearby(String lat, String lon) {
        // If geolocation is not set, use Plaza Santa Bárbara, 2, Madrid
        if(lat == null || lon == null || lat.equals("") || lon.equals("")) {
            lat = "40.427305";
            lon = "-3.696754";
        }

        // SOQL query to get the nearest warehouses
        String queryString =
            "SELECT Id, Name, Location__Longitude__s, Location__Latitude__s " +
            "FROM Account " +
            "WHERE DISTANCE(Location__c, GEOLOCATION("+lat+","+lon+"), \"km\") < 1 " +
            "ORDER BY DISTANCE(Location__c, GEOLOCATION("+lat+","+lon+"), \"km\") " +
            "LIMIT 10";

        // Run and return the query results
        return(database.Query(queryString));
    }
}

Página Visualforce FindNearbyAccountPage:

<apex:page sidebar="false" showheader="false" standardController="Account" extensions="FindNearby">
    <!-- Include in Google Maps API via JavaScript static resource -->
    <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js"></script>

    <!-- Setup the map to take up the whole window -->
    <style>
        html, body { height: 100%; }
        .page-map, .ui-content, #map-canvas { width: 100%; height:100%; padding: 0; }
        #map-canvas { height: min-height: 100%; }
    </style>

    <script>
        function initialize() {
            var lat, lon;

            // If we can, get the position of the user via device geolocation
            if (navigator.geolocation) {
                navigator.geolocation.getCurrentPosition(function(position){
                    lat = position.coords.latitude;
                    lon = position.coords.longitude;
                    console.log(lat);
                    console.log(lon);

                    // Use Visualforce JavaScript Remoting to query for nearby accounts
                    Visualforce.remoting.Manager.invokeAction("{!$RemoteAction.FindNearby.getNearby}", lat, lon,
                        function(result, event){
                            if (event.status) {
                                console.log(result);
                                createMap(lat, lon, result);
                            } else if (event.type === "exception") {
                                //exception case code
                            } else {
                            }
                        },
                        {escape: true}
                    );
                });
            } else {
                // Set default values for map if the device does not have geolocation capabilities
                /** Plaza Santa Bárbara, 2, Madrid **/
                lat = 40.427305;
                lon = -3.696754;

                var result = [];
                createMap(lat, lon, result);
            }
        }

        function createMap(lat, lon, accounts){
            // Get the map div, and center the map at the proper geolocation
            var currentPosition = new google.maps.LatLng(lat,lon);
            var mapDiv = document.getElementById("map-canvas");
            var map = new google.maps.Map(mapDiv, {
                center: currentPosition,
                zoom: 14,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            });

            // Set a marker for the current location
            var positionMarker = new google.maps.Marker({
                map: map,
                position: currentPosition,
                icon: "https://maps.google.com/mapfiles/ms/micons/green.png"
            });

            // Keep track of the map boundary that holds all markers
            var mapBoundary = new google.maps.LatLngBounds();
            mapBoundary.extend(currentPosition);

            // Set markers on the map from the @RemoteAction results
            var acc;
            for(var i=0; i<accounts.length;i++){
                acc = accounts[i];
                console.log(acc);
                setupMarker(acc, map, mapBoundary);
            }

            // Resize map to neatly fit all of the markers
            map.fitBounds(mapBoundary);
        }

        function setupMarker(acc, map, mapBoundary){
            var accountNavUrl;

            // Determine if we are in Salesforce1 and set navigation link appropriately
            try{
                if(sforce.one){
                    accountNavUrl =
                        "javascript:sforce.one.navigateToSObject(\"" + acc.Id + "\")";
                }
            } catch(err) {
                console.log(err);
                accountNavUrl = "\\" + acc.Id;
            }

            var accountDetails =
                "<a href=\"" + accountNavUrl + "\">" + acc.Name + "</a>";

            // Create the callout that will pop up on the marker
            var infowindow = new google.maps.InfoWindow({
                content: accountDetails
            });

            // Place the marker on the map
            var marker = new google.maps.Marker({
                map: map,
                position: new google.maps.LatLng(
                                acc.Location__Latitude__s,
                                acc.Location__Longitude__s)
            });
            mapBoundary.extend(marker.getPosition());

            // Add the action to open up the panel when it is marker is clicked
            google.maps.event.addListener(marker, "click", function(){
                infowindow.open(map, marker);
            });
        }

        // Fire the initialize function when the window loads
        google.maps.event.addDomListener(window, "load", initialize);
    </script>

    <!--  All content is rendered by the Google Maps code -->
    <!--  This minimal HTML justs provide a target for GMaps to write to -->
    <body style="font-family: Arial; border: 0 none;">
        <div id="map-canvas"></div>
    </body>
</apex:page>
Anuncios

JavaScript Remoting de Salesforce más allá de Visualforce y Apex. Proyectos Web en Force.com totalmente dinámicos.

JavaScript Remoting for Apex ControllersEn este post vamos a intentar explicar lo mejor posible qué es JavaScript Remoting, ventajas e inconvenientes de su utilización y alguna funcionalidad no documentada que hemos encontrado analizando el interior de la librería JavaScript que utiliza Salesforce en las páginas Visualforce.

¿Qué es JavaScript Remoting?

JavaScript Remoting es una funcionalidad de la plataforma Force.com de Salesforce que nos permite invocar métodos en controladores APEX desde JavaScript en nuestras páginas Visualforce. Dicho de otra forma, es una funcionalidad con la que podemos invocar métodos del servidor (APEX) desde la página Web cliente (página Visualforce) mediante peticiones AJAX.

Gracias a JavaScript Remoting podemos crear páginas con comportamiento complejo, dinámico y totalmente interactivas que no es posible con los componentes AJAX estándar de Visualforce.

Es importante tener en cuenta que la petición al controlador APEX se realiza de forma síncrona, pero la respuesta del servidor hacia la página Visualforce es asíncrona. Comprender esto es de vital importancia para  realizar un buen uso de esta funcionalidad.

La funcionalidad de JavaScript Remoting se puede dividir en 3 partes:

  • La invocación de los métodos remotos que se añaden a la página Visualforce, escrito en JavaScript.
  • La definición de los métodos remotos en la clase del controlador APEX. Esta definición de los métodos se escribe en código APEX.
  • La función JavaScript que se encargará de obtener y procesar la respuesta del controlador, escrita en JavaScript.

¿Para qué sirve JavaScript Remoting?

Fundamentalmente lo que conseguimos con JavaScript Remoting es poder recargar ciertas partes de una página Visualforce sin tener que recargar la página completa. De esta forma podemos crear páginas Visualforce muy complejas, totalmente dinámicas, con cualquier tipo de funcionalidad que requiera tener siempre los datos actualizados en la página Visualforce, y con una mínima transferencia de datos entre cliente y servidor, y todo esto sobre la plataforma Force.com.

Un buen ejemplo del uso de JavaScript Remoting es el Servicio de Empleo de élogos (Agencia Privada de colocación). Este vídeo producido por Salesforce da una visión general de él:

Para poder comprender la potencia de JavaScript Remoting, indicaros que para este proyecto utilizamos en su día una sola página Visualforce, dentro de la cual nos encontramos con numerosos componentes Visualforce customizables y entre las muchas funcionalidades que tiene, podemos encontrar Correo interno, Preguntas y respuestas, Calendario de eventos, Notificaciones, Gamificación, Chat, Videoconferencia, Agenda de contactos, Visor de documentos… y un largo etc… Toda la información se muestra en tiempo real, y con velocidades de carga y ejecución muy altas. A continuación os mostramos en imágenes algunas de estas funcionalidades:

El pase de diapositivas requiere JavaScript.

¿Cuáles son las ventajas de utilizar JavaScript Remoting?

Podemos destacar las siguientes ventajas:

  • Nos permite realizar numerosas actualizaciones sobre la página sin tener que recargar la página completa.
  • Ofrece mucha flexibilidad con lo que podemos crear comportamientos complejos y dinámicos.
  • El estado de la vista (View State) no se pasa al servidor, con lo que la velocidad de proceso es mucho mayor. Únicamente se pasan al servidor los parámetros que nosotros queramos. De igual forma, la respuesta del servidor sólo llevará los datos que nosotros necesitemos.
  • El código se ejecuta en el contexto del usuario que visualiza la página.
  • Se desvincula la página del controlador.
  • Los métodos admiten como argumentos tipos primitivos APEX, colecciones, sObjects genéricos y tipados, y clases e interfaces APEX definidas por el usuario.

¿Cuáles son los inconvenientes de utilizar JavaScript Remoting?

Podemos destacar las siguientes ventajas:

  • Al ser la respuesta del servidor asíncrona, se pueden producir conflictos o comportamientos inesperados, aunque en muy raras ocasiones.
  • Se requieren conocimientos de JavaScript.
  • Como no se pasa el estado de la vista al servidor, deberemos pasar por parámetros todos aquellos valores que ya tengamos en la página y que necesitemos en los métodos del controlador, para no volver a obtenerlos en el servidor.
  • La respuesta del servidor está limitada a un máximo de 15 Mb (en mi caso nunca he llegado a ese límite).
  • Debemos tener en cuenta que Salesforce lo considera como llamadas API, por lo que cuentan para el límite de llamadas API  de nuestra organización de Salesforce en un período de 24 horas. Vota esta idea para que Salesforce no las tenga en cuenta para el límite de llamadas API. Gracias a la valiosa ayuda de Carolina Ruiz, Salesforce ha confirmado que las llamadas de los métodos JavaScript Remoting no cuentan para el límite de llamadas API, lo cual es una gran noticia para todas aquellas organizaciones con límites muy bajos.

Sintaxis de JavaScript Remoting

Para poder utilizar JavaScript Remoting en nuestras páginas Visualforce, debemos agregar el siguiente código:

[namespace.]controller.method(
    [parameters...,]
    callbackFunction,
    [configuration]
);

Donde:

  • namespace: es el “espacio de nombres” de la clase del controlador. Se requiere si tu organización tiene definido un espacio de nombres o si la clase proviene de un paquete instalado.
  • controller: es el nombre de tu controlado APEX.
  • method: es el nombre del método APEX que queremos invocar.
  • parameters: es el listado de parámetros, separados por comas, que tu método APEX espera recibir.
  • callbackFunction: es el nombre de la función JavaScript que manejará la respuesta del controlador y recibirá el estado de la llamada al método (event) y el resultado del mismo (result), como parámetros:
    • result: es el objeto que contiene la respuesta del método del controlador APEX.
    • event: es el objeto que contiene el estado de la llamada. Tiene como campos:
      • status: si todo ha ido bien, si valor es true, en caso contrario false, lo cual indica que se ha producido algún error.
      • type: indica el tipo de respuesta; rpc para una respuesta satisfactoria, exception si se ha lanzado alguna excepción en el método remoto, etc…
      • message: si se ha producido algún error, aquí se devuelve el mensaje de error correspondiente.
      • where: contiene el seguimiento de pila APEX, si se generó por el método remoto.
  • configuration: se utiliza para modificar el comportamiento de la llamada remota. Entre los parámetros que podemos configurar, tenemos:
    • buffer (Boolean): si las invocaciones a los métodos remotos se ejecutan muy seguidas en el tiempo, Salesforce las agrupa en una sola petición y las ejecuta en el servidor una detrás de otra y en el mismo, de esta forma JavaScript Remoting optimiza las solicitudes que se ejecutan y se evitan problemas de concurrencia. El valor por defecto es true. Si configuramos su valor como false, Salesforce lanza una petición independiente por cada invocación de método remoting que hagamos, pero debemos prestar mucha atención en las peticiones que realizamos, porque podemos tener problemas de concurrencia.
    • escape (Boolean): se utiliza para “escapar” la respuesta del método de Apex. El valor por defecto es true.
    • timeout (Integer): es tiempo de espera para la solicitud, en milisegundos. El valor predeterminado es 30000 (30 segundos). El máximo es de 120000 (120 segundos).

Un ejemplo de código en la página Visualforce sería el siguiente:

/*** Página Visualforce ***/

TicMindController.getMessages(
    parameter1, parameter2, etc...,
    function(result, event) {
        if (event.status) {
            // El proceso se ha realizado correctamente.
            // Analizamos el resultado aquí (result).
        } else {
            // Se he producido algún error.
            // Analizamos el error aquí (event.message, event.where)
        }
    },
    {escape:false}
);

Donde:

  • TicMindController: es el nombre de la clase del controlador APEX.
  • getMessages: es el nombre del método APEX que invocamos.

El código de ejemplo del controlador APEX sería el siguiente:

/*** Controlador APEX ***/

public with sharing class TicMindController
    @RemoteAction
    public static String getMessages(String parameter1, String parameter2, etc...){
        // Procesamos y devolvemos la respuesta
    }
}

Algunas anotaciones sobre el código del método APEX remoto:

  • El método remoto debe tener la anotación @RemoteAction.
  • El método remoto debe ser estático.
  • El método remoto debe ser público o global, según sea el alcance de nuestro método. Si el método es global, debemos tener en cuenta que la clase también debe ser global.
  • Si vamos a invocar métodos remotos en alguna página Visualforce incrustada dentro de un iframe, es importante tener en cuenta que los métodos remotos deben de ser globales, sino, se producirán errores, como los siguientes:
    • Visualforce Remoting: Javascript proxies were not generated for controller TicMindController: may not use public remoted methods inside an iframe (este mensaje es muy claro).
    • Unable to invoke action ‘TicMindController.getMessages’: no controller and/or function found.

Existe otra forma de invocar a los métodos remotos desde JavaScript, y es usando directamente el API Remoting de Visualforce. En el siguiente ejemplo podemos ver la misma llamada remota explicada anteriormente, pero usando el API de Visualforce. Se puede apreciar que no existen grandes cambios entre ambas sintaxis:

/*** Página Visualforce ***/

Visualforce.remoting.Manager.invokeAction(
    '{!$RemoteAction.TicMindController.getMessages}',
    parameter1, parameter2, etc...,
    function(result, event) {
        if (event.status) {
            // El proceso se ha realizado correctamente.
            // Analizamos el resultado aquí (result).
        } else {
            // Se he producido algún error.
            // Analizamos el error aquí (event.message, event.where)
        }
    },
    {escape:false}
);

Como he indicado anteriormente, los métodos remotos admiten como parámetros no sólo los tipos primitivos APEX, sino también colecciones, sObjects genéricos y tipados, y clases e interfaces APEX definidas por el usuario. Un buen ejemplo de cómo se trabaja con algunos de estos tipos de parámetros lo podemos encontrar en este blog.

Funcionalidad no documentada de JavaScript Remoting

Ante todo tengo que indicar que al ser una funcionalidad no documentada, no es oficial, y puede ser modificada por Salesforce sin previo aviso. Su uso es bajo nuestra propia responsabilidad.

A veces puede que se produzcan problemas de conexión con los servidores de Salesforce desde nuestra máquina. Lo normal es que sean micro cortes de nuestra propia red, y no tengamos acceso momentáneamente a Salesforce. Si esto ocurre en una invocación de un método remoto, se lanzaría una excepción y el proceso finalizaría, ya que el API de Visualforce no vuelve a intentarlo, lo cual a veces es un problema. Para forzar que se realicen un número determinado de reintentos ante esta situación, existe, dentro del API de Visualforce, la siguiente variable llamada maxRetries ,que podemos encontrar aquí:

  • Visualforce.remoting.last.maxRetries: el valor por defecto es 0. Si queremos que se realicen 3 reintentos por ejemplo, debemos asignar a la variable un 2, ya que el contador comienza en 0.

Insisto en que no es oficial, y en la siguiente release que haga Salesforce puede que esto cambie. A fecha de hoy está probado con la Winter 14.

API propio para el uso de JavaScript Remoting

Hace tiempo creé un API propio para poder optimizar en la mayor medida posible el uso que hago de esta técnica de Salesforce. Como podréis observar en el código, dentro de la función que maneja la respuesta del método remoto, escribo en la consola del explorador el tiempo que se ha tardado en completar la solicitud. Esto es más que nada informativo, pero nos puede dar una idea de la velocidad de la red y el tiempo de proceso interno de Salesforce en ejecutar el método.

Además, podéis observar un pequeño API JavaScript para escribir en la consola del explorador y para guardar las excepciones que se producen en un objeto de Salesforce, más que nada para tener registrados todos los errores que se producen en nuestra aplicación, sobre todo en la fase de desarrollo y fase beta.

Aquí os dejo mi código por si a alguno le pueda venir bien:

API JavaScript para escribir en la consola y registrar las excepciones:

/**************/
/*   DEBUG    */
/**************/
// Namespace del debug y la consola del explorador
ticMind.api.debug = {
    /*** Variables ***/
    isDebug: true,
    console: {
        /*** Métodos ***/
        // Escribimos en la consola del explorador
        log: function (message) {
            if (ticMind.api.debug.isDebug && window.console && window.console.log) {
                window.console.log(this.getMessage(message));
            }
        },
        debug: function (message) {
            if (ticMind.api.debug.isDebug && window.console && window.console.debug) {
                window.console.debug(this.getMessage(message));
            }
        },
        info: function (message) {
            if (ticMind.api.debug.isDebug && window.console && window.console.info) {
                window.console.info(this.getMessage(message));
            }
        },
        warn: function (message) {
            if (ticMind.api.debug.isDebug && window.console && window.console.warn) {
                window.console.warn(this.getMessage(message));
            }
        },
        error: function (message) {
            if (ticMind.api.debug.isDebug && window.console && window.console.error) {
                window.console.error(this.getMessage(message));
            }
        },
        // Devolvemos la hora con milisegundos para la consola
        getTimeString: function () {
            var now = new Date();
            var hours = now.getHours();
            var minutes = now.getMinutes();
            var seconds = now.getSeconds();
            var milliseconds = now.getMilliseconds();
            if (hours < 10) { hours = '0' + hours; }
            if (minutes < 10) { minutes = '0' + minutes; }
            if (seconds < 10) { seconds = '0' + seconds; }
            if (milliseconds < 10) { milliseconds = '0' + milliseconds; }             return (hours + ":" + minutes + ":" + seconds + "." + milliseconds);         },         // Devolvemos el mensaje para la consola, con la app más la hora actual, más el propio mensaje         getMessage: function (message) {             return ('TicMind: ' + this.getTimeString() + ' --> ' + ((message) ? message : ''));
        }
    },
    system: {
        /*** Variables ***/
        // Variable que contiene el método que se va a lanzar para guardar el error en el sistema. Debe de tener 3 parámetros de entrada (description, className, method)
        saveExceptionMethod: null,

        /*** Métodos ***/
        // Guardamos el error en el objeto Exception
        saveException: function(description, className, method, event){
            // Comprobamos si tenemos método
            if (!(this.saveExceptionMethod)){
                ticMind.api.debug.console.error('No se ha configurado el método encargado de registrar los errores en el sistema (ticMind.api.debug.system.saveExceptionMethod). Debe tener los siguientes parámetros de entrada: description, className, method.');
                return;
            }
            if (!(description)){return;}
            if ((event) && (event.message)){
                description += '. Excepción producida: ' + event.message + ((event.where) ? '. Traza: ' + event.where : '');
            }
            // Lanzamos método
            this.saveExceptionMethod(description, className, method);
        }
    }
};

API JavaScript para ejecutar métodos remotos:

/**************/
/*  REMOTING  */
/**************/
// Namespace remoting
ticMind.api.remoting = {
    /*** Variables ***/
    buffer: true,
    timeout: 30000,

    /*** Métodos ***/
    // Modificamos el número máximo de reintentos (por defecto 0). 2 sería 3 intentos, contando el 0 como el primero
    setMaxRetries: function (maxRetries) {
        if ((typeof Visualforce != typeof undefined) && (Visualforce.remoting) && (Visualforce.remoting.last) && (Visualforce.remoting.last.maxRetries)) {
            Visualforce.remoting.last.maxRetries = maxRetries;
        }
    },
    // Invocamos el método remoting
    invokeAction: function (action, params, callbackOK, callbackKO, escape) {
        // Si no tenemos acción, salimos
        if (!(action)) {
            ticMind.api.debug.console.log('Error al invocar el método remoto en ticMind.api.remoting.invokeAction. No vienen datos en el parámetro "action"');
            return;
        }

        // Si no tenemos la librería remoting cargada
        if ((typeof Visualforce == typeof undefined) || (!Visualforce.remoting) || (!Visualforce.remoting.Manager) || (!Visualforce.remoting.Manager.invokeAction)){
            ticMind.api.debug.system.error('No existe el método Visualforce.remoting.Manager.invokeAction() en ticMind.api.remoting.invokeAction(). Posiblemente no se haya cargado la librería de Salesforce de Remoting.');
        }

        // Creamos array de argumentos
        var args = [];

        // Añadimos acción
        args[args.length] = (action);

        // Añadimos parámetros
        if (params) { args.push.apply(args, params); }

        // Añadimos debug
        args[args.length] = (ticMind.api.debug.isDebug);

        // Comenzamos temporizador para ver lo que tarda en ejecutarse el método remoting
        var startTime = (new Date()).getTime();
        var dateTimeDiff = null;

        // Añadimos función de respuesta. En la llamada a la callback añadimos un segundo parámetro con el tiempo invertido en la llamada remota
        args[args.length] = function (result, event) {
            // Tiempo empleado en la llamada remoting
            dateTimeDiff = (new Date()).getTime() - startTime;

            // Evaluamos resultado
            if (event.status) {
                // OK
                if ((result == null) || (typeof result.success == (typeof undefined))) {
                    // Resultado OK
                    if (callbackOK) {callbackOK(result, dateTimeDiff);}

                    // Debug
                    ticMind.api.debug.console.log('OK --> Método "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Resultado: ' + result);
                }else{
                    // En result viene el objeto con los campos (success, data, error)
                    if (result.success){
                        // Resultado OK
                        if (callbackOK) {callbackOK(result.data, dateTimeDiff);}

                        // Debug
                        ticMind.api.debug.console.log('OK --> Método "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Resultado: ' + result.data);
                    }else{
                        // Hubo algún error
                        if (callbackKO) {callbackKO(result.error, dateTimeDiff);}

                        // Si tenemos debug, registramos el error
                        ticMind.api.debug.console.log('KO --> Método "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Error: ' + event.message + ((event.where) ? '. Traza: ' + event.where : ''));

                        // Registramos el error en el sistema
                        var arr = action.split('.');
                        var className = action;
                        var method = action;
                        if (arr.length == 2){
                            className = arr[0];
                            method = arr[1];
                        }
                        ticMind.api.debug.system.saveException('Error al invocar el método remoto "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Error: ' + event.message + ((event.where) ? '. Traza: ' + event.where : ''), className, method);
                    }
                }
            } else {
                // Error
                if (callbackKO) { callbackKO(event.message, dateTimeDiff); }

                // Si tenemos debug, registramos el error
                ticMind.api.debug.console.log('KO --> Método "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Error: ' + event.message + ((event.where) ? '. Traza: ' + event.where : ''));

                // Registramos el error en el sistema
                var arr = action.split('.');
                var className = action;
                var method = action;
                if (arr.length == 2){
                    className = arr[0];
                    method = arr[1];
                }
                ticMind.api.debug.system.saveException('Error al invocar el método remoto "' + action + '". Tiempo de proceso: ' + dateTimeDiff + '. Error: ' + event.message + ((event.where) ? '. Traza: ' + event.where : ''), className, method);
            }
        };

        // Añadimos escape
        var esc = (typeof escape != typeof undefined) ? escape : true;
        args[args.length] = { escape: esc, buffer: this.buffer, timeout: this.timeout };

        // Invocamos método remoto
        Visualforce.remoting.Manager.invokeAction.apply(Visualforce.remoting.Manager, args);
    }
};

Y un ejemplo de uso de mi API Remoting sería el siguiente:

ticMind.api.remoting.invokeAction('TicMindController.getMessages',
                                   [
                                       parameter1,
                                       parameter2,
                                       parameter3,
                                       etc...
                                   ],
                                   function(result, dateTimeDiff){ // Callback OK },
                                   function(error, dateTimeDiff){ // Callback KO },
                                   false);

Como parámetros del método invokeAction tenemos los siguientes:

  • action: nombre del controlador seguido del nombre del método APEX  remoto a invocar.
  • params: array de parámetros a pasar al método APEX.
  • callbackOK: función a la que se llama cuando el método remoto se ejecuta de forma correcta. Esta función recibirá 2 parámetros, result (resultado) y dateTimeDiff (tiempo en milisegundos en ejecutarse el método).
  • callbackKO: función a la que ha producido algún error al ejecutarse el método remoto. Esta función recibirá 2 parámetros, error (mensaje de error) y dateTimeDiff (tiempo en milisegundos en ejecutarse el método).
  • escape: indicamos si se va a escapar el resultado (true, false).

Si quisiéramos aumentar a 3 el número de reintentos en la llamada remoting, tendríamos que hacer lo siguiente:

ticMind.api.remoting.setMaxRetries(2);

Para deshabilitar el buffer de llamadas remoting:

ticMind.api.remoting.buffer = false;

Para aumentar el timeout al máximo de 2 minutos:

ticMind.api.remoting.timeout = 120000;

Y aquí concluye este post. Espero que no os haya resultado muy pesado de leer… y cualquier duda, ya sabéis, en ticMind estamos para ayudaros.

Entendiendo Salesforce communities

En el pasado Dreamforce, Salesforce sacaba a la luz Communities, durante mi visita a Dreamforce del año pasado, tuve la suerte de reunirme con los Product Managers para conocer de primera mano este producto. Durante unos meses hemos participado en el piloto interno de la herramienta aprendiendo las nuevas posibilidades que este nuevo concepto de colaboración nos podía ofrecer y realizando diferentes demos para testear la forma en la que podía sernos útil este nuevo tipo de licenciamiento, como conclusión, podemos decir que Communities mejora notablemente nuestros antiguos portales de cliente y partners, añadiéndoles la posibilidad de ser customizados por completo de una manera sencilla o de una manera más profunda ya sea usando site.com o desarrollando por completo nuestras páginas con apex, además nos da la posibilidad de agregar a nuestros portales funcionalidades hasta ahora imposibles con las anteriores licencias como por ejemplo:

Chatter
Chatter answers
Ideas
Salesforce Knowledge
Autenticación delegada
Customizaciones de Url, Emails, Página de login etc.

Cabe destacar que todo esto se puede usar  de una forma segura y segmentada, de esta forma Communities nos permite crear grupos acotados de usuarios que colaboren entre sí ya sean clientes, partners o empleados internos de nuestra organización. Entre las muchas utilidades que se le puede dar a Communities, destacan por ejemplo las comunidades de atención al cliente, podemos tener una comunidad donde nuestros clientes se ayuden entre sí, abaratando nuestro servicio de atención al cliente y mejorándolo considerablemente, como ejemplo os muestro una de las comunidades que hemos construido a modo de demostración: En primer lugar,  como podemos ver en la siguiente imagen, el look and feel de la página de login es totalmente custom y al estar trabajando sobre un site, nada nos impediría mostrar datos dinámicos como noticias o información que saliera de nuestros objetos de Salesforce permitiéndonos crear una home tan completa y dinámica como queramos.

Comlogin

Al acceder a nuestra comunidad, se accede a una pantalla donde se pueden ver las soluciones dadas de alta en nuestra organización en forma de árbol de navegación, de esta forma modificar los contenidos de esta página se hará de manera sencilla y dinámica, además al tener los datos del cliente que está accediendo en nuestra comunidad, en función de los productos que tengan contratados con nosotros, verán una información personalizada, dando un mejor servicio, mejorando la experiencia de usuario y mejorando la efectividad de los mensajes comerciales que se les quiera mostrar. Según el usuario navega por el árbol de soluciones, obtendrá ayuda en modo de vídeos y artículos, los comentarlos y valoraciones, así como todo el registro de navegación de los usuarios, queda almacenado en nuestra organización, dándonos una idea de la utilidad de nuestras soluciones y el interés por nuestros productos. Esta comunidad está construida sobre licencias “Customer Communities” con lo que hemos podido dotarle de la potencia de Service Cloud y de esta forma podemos redirigir los casos al equipo que mejor preparado esté para resolverlos de manera automática en función del lugar desde donde se crearon, también hemos integrado el Live Agent de Salesforce e implantado la Service Cloud Console para la gestión interna de incidencias por parte de los agentes.

FAQ

Al tener disponible Chatter en este nuevo tipo de licenciamiento, hemos creado una pantalla desde la cual pueden acceder a los grupos de Chatter a los que pertenecen, como se puede ver en las imágenes, el selector de grupos también ha sido customizado mostrando únicamente la información necesitemos:

ComChatter

ComChatterGroup

Por último os mostramos el uso de Chatter Answers desde nuestro portal, de esta forma los usuarios pueden realizar preguntas a la comunidad y responderse las dudas entre ellos,  así como consultar dudas anteriormente resueltas a otros usuarios.

ComPreguntas

Como podéis ver, una comunidad nos permite dar a nuestros clientes y socios unos servicios que hasta hace poco nos habría costado meses construir de una manera sencilla y segura, este es el caso que os he presentado, pero con Communities también podemos hacer partícipes a nuestros usuarios de nuestros procesos de negocio dándoles no solo funcionalidad añadida sino enriqueciendo nuestra base de datos y nuestros procesos con su participación en los mismos.

Por último, os comparto este video de demostración de Salesforce sobre communities, donde nos podemos hacer una idea más general de las funcionalidades que Communities nos brinda. No dudéis en poneros en contacto con nosotros ante cualquier duda/pregunta.