Mostrando las entradas con la etiqueta Javascript. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Javascript. Mostrar todas las entradas

viernes, 6 de diciembre de 2013

Mapa con geolocalización


En esta entrada crearemos un mapa de Google Maps utilizando la API versión 3.14 y ubicaremos un marcador en la posición actual del usuario utilizando la geolocalización del dispositivo.

Para comenzar crearemos un sitio web en VS2010 o VS2012 y agregaremos tres carpetas: css, img y js. En la carpeta js colocaremos jQuery, creamos un archivo map.js y también lo agregamos a la carpeta js. Creamos un archivo de hoja de estilos llamado site.css y lo colocamos en nuestra carpeta css.

Para el icono de la posición actual del usuario podemos utilizar uno de los que aparecen en el sitio http://mapicons.nicolasmollet.com, tienen un diseño atractivo y se puede personalizar el color de fondo. Una vez elegido el icono lo guardamos en nuestra carpeta img con el nombre current_location.png

Una vez realizados estos pasos nuestro Explorador de Soluciones debería verse de la siguiente forma:

Vamos a incorporar todos los elementos junto con la referencia a la API de Google Maps en nuestra página HTML con el siguiente código:
<head> <title>Ubicación actual</title> <script src="http://maps.google.com/maps/api/js?v=3.14&sensor=false&language=es-mx" type="text/javascript"></script> <script src="js/jquery-1.10.2.min.js" type="text/javascript"></script> <link href="css/site.css" rel="stylesheet" type="text/css" /> <script src="js/map.js" type="text/javascript"></script> </head> <body> <div id="map"></div> </body> </html>

Vamos a abrir nuestra hoja de estilos y vamos a colocar algunos estilos para que el mapa ocupe la pantalla completa del dispositivo:
html, body { margin: 0px; height: 100%; } #map { width: 100%; height: 100%; }

Ahora vamos a generar el código que nos permitirá la creación del mapa, la localización de la ubicación del usuario y la colocación del respectivo marcador en el mapa.

Abriremos nuestro archivo map.js y comenzaremos con la obtención de la ubicación actual del usuario:
///<reference path="jquery-1.10.2.min.js" /> var blogger = blogger || {}; blogger.start = function () { if (window.clientInformation) { window.clientInformation.geolocation.getCurrentPosition( function (e) { var latlng = new google.maps.LatLng(e.coords.latitude, e.coords.longitude); initialize(latlng); }, function (e) { initialize(null); }, { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 } ); } else { window.navigator.geolocation.getCurrentPosition( function (e) { var latlng = new google.maps.LatLng(e.coords.latitude, e.coords.longitude); initialize(latlng); }, function (e) { initialize(null); }, { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 } ); } function initialize(e) { } } $(document).ready(function () { blogger.start(); });

En la primera línea de código estamos agregando una referencia a la librería jQuery para que el editor tome la definición de las funciones de esa librería y nos facilite la codificación.

Estamos creando un espacio de nombres llamado blogger para que el manejo de nuestras variables y funciones no vayan a causar conflicto con otras que estén depositadas en el espacio global. Una vez que haya finalizado la carga de la página estamos mandando a llamar a la función blogger.start que será la encargada de todo el trabajo.

Estamos validando que exista la propiedad window.clientInformation para poder diferenciar entre el navegador Safari y los demás, veamos que cuando no existe esa propiedad la obtención de la localización actual del usuario se hace a través del objeto window.navigator

La función getCurrentPosition recibe tres argumentos, el primero marca la función que se mandará a llamar cuando se haya podido obtener la posición del usuario, el segundo es la función que se ejecutará cuando no se haya podido obtener la localización del usuario y el tercero es un conjunto de opciones que nos permiten establecer si queremos la mayor exactitud posible, el tiempo en milisegundos que esperaremos para obtener la posición y el tiempo que esta posición estará guardada en caché.

Veamos que cuando la localización haya sido exitosa, se estará construyendo un objeto google.maps.LatLng, este objeto nos permite establecer ubicaciones en la notación (Latitud, Longitud) en el mapa.

Ahora vamos a codificar nuestra función initialize que creará el mapa y el marcador con la ubicación actual del usuario.

Comenzaremos con la creación del mapa centrándolo en la ubicación del usuario con un zoom de 14:
function initialize(e) { var mapOptions = { center: e, zoom: 14 }; blogger.map = new google.maps.Map(document.getElementById('map'), mapOptions); }

Veamos que el constructor del objeto google.maps.Map nos pide el nodo donde se alojará el mapa (en nuestro caso un div cuyo id es "map") y también un literal object que tenga la configuración del mapa. Vamos a abrir nuestro archivo index.htm con el navegador y veamos que nos pedirá la autorización para enviar nuestra localización a la página web, esto es completamente normal.

Ahora vamos a crear un marcador que utilice la imagen que tenemos en la carpeta img y lo colocaremos en la posición actual del usuario: function initialize(e) { var mapOptions = { center: e, zoom: 14 }; blogger.map = new google.maps.Map(document.getElementById('map'), mapOptions); var markerOptions = { position: e, map: blogger.map, title: 'Usted se encuentra aquí', icon: 'img/current_location.png' } var currentPositionMarker = new google.maps.Marker(markerOptions); }

El constructor del objeto google.maps.Marker nos pide un objeto literal que traiga la configuración del marcador, en este caso nos pide la posición del marcador (google.maps.LagLnt), el mapa donded estará localizado el marcador (blogger.map), el texto que mostrará como tooltip del marcador y la url del icono que queremos utilizar. En caso de que no utilicemos el atributo "icon", entonces se colocará el marcador que tiene por defecto Google Maps.

Si visitamos de nueva cuenta nuestro sitio, veremos que colocará el marcador en nuestra posición actual.

Espero te haya resultado de utilidad esta entrada, actualmente muchas aplicaciones están requiriendo del uso de mapas y Google Maps es una opción muy buena dada la facilidad que tiene su API para ser utilizada.

¡Nos vemos la siguiente!

jueves, 5 de diciembre de 2013

Web Workers


En esta entrada veremos la implementación de un Worker de HTML5 muy sencillo pero con implicaciones de conocimiento que vale la pena aprender.

Los Worker se hicieron para ejecución de tareas asíncronas que se ejecutarán en un hilo ajeno al que está ejecutando la página web. Tan ajeno que desde el Worker no tenemos acceso al DOM de la página, es decir que toda la sincronización se hace a través de mensajes, de objetos que sean serializables. Por excelencia utilizaremos JSON.

El problema planteado es que necesitamos realizar peticiones cada 10 segundos a un endpoint el cual devolverá un valor estadísticamente único, simulando que debemos estar checando el servidor para ver si hay datos nuevos para actualizar la interfaz del usuario.

Para esto vamos a preparar nuestro entorno de desarrollo (VS2010 o VS2012):

  1. Crear un sitio web
  2. Agregar una carpeta llamada js
  3. Agregar un archivo index.htm
  4. Agregar un Generic Handler llamado GetRandom.ashx
  5. Agregar jQuery a la carpeta js
  6. Agregar dos archivos js a la carpeta: tools.js y randomWorker.js

El archivo tools.js será el que esté en comunicación con el randomWorker, éste último es el que realizará las peticiones a GetRandom.ashx para un valor nuevo.

Prepararemos nuestro archivo index.htm con el siguiente código:
<head> <title>Valor aleatorio a través de Worker</title> <script src="js/jquery-1.7.2.min.js" type="text/javascript"></script> <script src="js/tools.js" type="text/javascript"></script> </head> <body> <p>Este es el valor aleatorio generado desde el servidor: <span id="lblAleatorio"></span></p> </body>

Vamos a abrir nuestro randomWorker.js para programar la tarea asíncrona que se estará ejecutando cada 10 segundos:
self.onmessage = function (e) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { postMessage(JSON.parse(xmlhttp.response)); } } var url = '../GetRandom.ashx'; xmlhttp.open('GET', url); xmlhttp.send(); }

Estamos creando una instancia de XMLHttpRequest para realizar las peticiones a GetRandom.ashx. ¿Por qué no utilizamos $.ajax?, porque jQuery está basado en el DOM y por lo tanto no puede ser utilizado desde un Worker.

Estamos asignando un manejador al evento onreadystatechange que se dispara cada vez que hay un cambio en el estado de la petición. Los valores para readyState son los siguientes:

0: Sin inicializar, es decir que el método open no ha sido llamado.
1: Cargando, ya fue abierto pero el método send no ha sido llamado.
2: El método send ya fue llamado.
3: La respuesta está siendo descargada.
4: Todas las operaciones han finalizado.

El atributo status establece un valor para el tipo de respuesta que se está recibiendo. Los códigos de estado pueden tener los siguientes valores:

1xx: El servidor ya recibió las cabeceras y se puede proceder con el envío del cuerpo de la petición.
2xx: La solicitud fue recibida por el servidor y fue correcta.
3xx: Para terminar la solicitud el cliente debe de tomar acciones adicionales. Generalmente encontraremos que se debe a que el recurso ya fue cambiado de dirección.
4xx: La sintaxis de la petición no es la correcta o bien esta no puede ser procesada. Esto indica un error del lado del cliente.
5xx: Ocurrió un error del lado del servidor.

Tomando en cuenta lo anterior, el readyState que nos interesa es el 4 y el status es el 200, es por ello que esa validación se está haciendo en el if.

Nuestro GetRandom.ashx estará devolviendo información en formato JSON por lo que procedemos a procesarla y con la función postMessage se la enviamos al cliente del Worker (tools.js)

Notemos también que no estamos utilizando la función window.setInterval para programar la petición cada 10 segundos, eso es debido a que en el Worker no tenemos acceso al DOM y por lo tanto no tenemos acceso al objeto window.

Abrimos nuestro archivo tools.js y colocamos el siguiente código:
var tools = tools || {}; tools.Worker = new Worker('js/randomWorker.js'); tools.Worker.onmessage = function (m) { $('#lblAleatorio').text(m.data.value); }; tools.Worker.onerror = function (e) { alert('Ocurrió un problema durante la ejecución del Worker'); } $(document).ready(function () { tools.Worker.postMessage('getRandomValue'); window.setInterval(function () { tools.Worker.postMessage('getRandomValue'); }, 10000); });

Estamos creando un espacio de nombres "tools" para que cualquier variable que utilicemos no entre en conflicto con otra que esté en la página. Después estamos creando una instancia del objeto Worker que será el encargado de ejecutar la tarea asíncrona establecida en el archivo randomWorker.js.

Estamos asignando un manejador al evento onmessage del objeto Worker que recibirá el resultado de la ejecución asíncrona del Worker. Notemos que el resultado viene en la propiedad data. El resultado de la ejecución la colocaremos en nuestro elemento span con el id lblAleatorio.

Por si hay algún error, estamos también estableciendo un manejador para el evento onerror. Al finalizar la carga de la página, estamos realizando una petición inicial y dejamos programada una nueva petición en intervalos de 10 segundos. El método postMessage requiere que le sea pasado un argumento, entonces estamos enviando un texto muy sencillo y que realmente en este ejercicio no tiene la mayor importancia porque de él no depende alguna ejecución por parte del Worker.

Finalmente vamos a programar nuestro GetRandom.ashx con el siguiente código:
public class GetRandom : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "application/json"; context.Response.Write("{\"value\": \"" + Guid.NewGuid().ToString() + "\"}"); } public bool IsReusable { get { return false; } } }

Veamos que el ContentType de la respuesta está establecido en el MIME que necesita JSON, también deberemos notar que estamos generando un valor estadísticamente único con el objeto Guid.

Voy a visitar el archivo index.htm con IE 11. Esto es debido a que quiero hacer notar algo en particular con este navegador:

Ejecución con Internet Explorer 11

Podemos esperar más tiempo pero veremos que el valor único no es actualizado, esto se debe al caché del navegador. ¿Cómo podemos probarlo?, vamos a abrir las herramientas de desarrollo presionando F12 y activando la captura de red:

Captura del tráfico de red con la herramienta para desarrolladores de Internet Explorer 11

¿Qué está pasando?, podemos ver que el status es 304, esto significa que la petición está siendo servida desde el caché del navegador y no está yendo hasta el servidor por el nuevo valor. Para corregir este detalle vamos a ajustar el código de GetRandom.ashx de la siguiente manera:
context.Response.ContentType = "application/json"; context.Response.AppendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); context.Response.AppendHeader("Pragma", "no-cache"); context.Response.AppendHeader("Expires", "0"); context.Response.Write("{\"value\": \"" + Guid.NewGuid().ToString() + "\"}");

Una vez realizado el cambio volvemos a visitar nuestro index.htm y veremos que cada 10 segundos el valor será actualizado.

Este ha sido un ejercicio muy sencillo pero aborda varias cosas que vale la pena notar:

  1. No tenemos acceso al DOM desde un Worker.
  2. No podemos utilizar jQuery en un Worker dado que jQuery está basado en el DOM.
  3. La comunicación entre el Worker y la página (en nuestro caso tools.js) debe ser a través de mensajes de texto. Es por ello que se recomienda el uso de JSON.
  4. Deshabilitamos el caché en nuestro Handler para evitar que el navegador no realice la petición al servidor al despachar el resultado desde el caché.

Espero les haya resultado de mucha utilidad, nos vemos la siguiente.

miércoles, 4 de diciembre de 2013

Variables privadas en javascript



En esta entrada quiero abordar el tema de la implementación de variables privadas en un objeto javascript. Seguramente para muchas personas resultará muy básico, pero en proyectos que he participado y en los cursos que imparto me he encontrado que no se conoce esta característica de los objetos implementados en javascript.

Comenzaremos con un objeto sumamente sencillo y plantearemos algunas interrogantes que resultarán interesantes:
function Persona(nombre, apellido, edad) { this.Nombre = nombre; this.Apellido = apellido; this.Edad = edad; }

Notemos que se está declarando un objeto Persona que tendrá tres propiedades públicas: Nombre, Apellido y Edad. Podemos perfectamente validar la información que estamos recibiendo antes de asignarla a nuestras propiedades de la siguiente manera:
function Persona(nombre, apellido, edad) { this.Nombre = nombre; this.Apellido = apellido; if (!isNaN(edad) && Number(edad) > 18) { this.Edad = edad; } else { this.Edad = null; } } var prueba = new Persona('Pepe', 'Pérez', 30); if (prueba.Edad) alert('La edad es: ' + String(prueba.Edad)); else alert('La edad no fue asignada');

Si primero lo probamos con 30 veremos que la edad es correctamente asignada, pero si le asignamos un valor menor o igual a 18 o bien una palabra, veremos que la propiedad Edad tendrá un valor null.

Hasta ahora todo va bien, pero, ¿qué pasa si una vez que es construido el objeto le asignamos un valor no válido a la propiedad pública Edad?:
var prueba = new Persona('Pepe', 'Pérez', 'Hola'); if (prueba.Edad) alert('La edad es: ' + String(prueba.Edad)); else { alert('La edad no fue asignada'); prueba.Edad = 'Hola'; alert('La edad es: ' + prueba.Edad); }

Veremos que el valor 'Hola' habrá sido asignado a Edad sin problema alguno.

Para evitar este problema y validar la información antes de que ésta sea actualizada en nuestros objetos, podemos implementar miembros privados expuestos a través de funciones públicas. En este caso lo haremos con la Edad:
function Persona(nombre, apellido, edad) { this.Nombre = nombre; this.Apellido = apellido; var m_Edad = null; if (!isNaN(edad) && Number(edad) > 18) { m_Edad = edad; } this.getEdad = function () { return m_Edad; } this.setEdad = function (e) { if (!isNaN(edad) && Number(edad) > 18) { m_Edad = edad; } } }

Veamos que ya no existe la propiedad pública Edad, ahora tenemos un miembro privado llamado m_Edad el cual es leído a través de la función getEdad y actualizado a través de la función setEdad. Dentro de la función setEdad estamos validando que sea una edad válida, una vez que pasó nuestra validación, procedemos a asignar el valor. Probemos con el código que habíamos escrito antes con unas pequeñas adecuaciones para utilizar las nuevas funciones:
var prueba = new Persona('Pepe', 'Pérez', 'Hola'); if (prueba.getEdad()) alert('La edad es: ' + String(prueba.getEdad())); else { alert('La edad no fue asignada'); prueba.setEdad('Hola'); alert('La edad es: ' + prueba.getEdad()); }

Veremos que el segundo mensaje mostrará 'La edad es: null', esto se debe a que la edad 'Hola' no fue asignada a nuestro miembro privado m_Edad debido a que no era un valor válido.

Espero te haya resultado de utilidad esta pequeña entrada, nos vemos la siguiente.

domingo, 10 de noviembre de 2013

Herencia en javascript


Me he encontrado que es muy común que los programadores que trabajan para el ambiente web tengan la idea de que la herencia en javascript no es posible, pero realmente sí existe, su implementación no es tan evidente como en C# pero sí se puede realizar.

Vamos a crear una clase que se llamará Vehicle de la que heredaremos para crear la clase Car y Boat.

Comencemos por crear un proyecto en Visual Studio de tipo ASP NET Empty Web Site, ahí agregaremos una página llamada Inheritance.htm y comenzaremos abriendo una etiqueta para código javascript.

La clase Vehicle va a tener las propiedades:
  1. Model.
  2. Year.
  3. EngineSize.
También tendrá las siguientes funciones:
  1. IsStarted(), la cual devolverá un booleano indicando si el motor está encendido.
  2. Start(), marcará el motor del vehículo como encendido.
  3. Stop(), marcará el motor del vehículo como apagado.
Comenzaremos por crear la clase Vehicle con sus tres miembros públicos:
function Vehicle(model, year, engineSize) { this.model = model; this.year = year; this.engineSize = engineSize; }
Ahora vamos a agregar un miembro privado que guardará el estado que tiene el motor del vehículo y las funciones que permitirán la administración de ese estado:
function Vehicle(model, year, engineSize) { this.model = model; this.year = year; this.engineSize = engineSize; var engineIsStarted = false; this.isStarted = function() { return engineIsStarted; } this.start = function () { engineIsStarted = true; } this.stop = function () { engineIsStarted = false; } }
Aquí deberemos notar que al declarar engineIsStarted como variable, se convierte en un miembro privado y por lo tanto el que utilice una instancia de nuestra clase Vehicle no podrá manipular de forma directa a esta variable, sólo a través de las funciones que hemos creado.
Hasta ahora hemos creado lo que normalmente se haría para la declaración de una clase con algunos miembros públicos, un miembro privado y algunas funciones.
Para comenzar con la implementación de la herencia, deberemos empotrar el código que llevamos en una IIFE (Immediately Invoked Function Expression), asignarlo a una variable y regresar la instancia de la ejecución de la IIFE.    Nuestro código queda de la siguiente manera:
var Vehicle = (function(){ function Vehicle(model, year, engineSize) { this.model = model; this.year = year; this.engineSize = engineSize; var engineIsStarted = false; this.isStarted = function () { return engineIsStarted; } this.start = function () { engineIsStarted = true; } this.stop = function () { engineIsStarted = false; } } return Vehicle; }());
Ahora vamos a crear nuestra clase Car que heredará de Vehicle y aparte agregará un miembro público llamado wheelCount.    Comenzaremos creando nuestra clase Car como habitualmente lo haríamos, con su constructor recibiendo las cuatro propiedades que va a tener un carro (model, year, engineSize, wheelCount) pero sólo crearemos un miembro público llamado wheelCount dado que los otros tres los heredaremos de Vehicle:
function Car(model, year, engineSize, wheelCount) { this.wheelCount = wheelCount; }
Ahora vamos a empotrar el código en una IIFE tal como lo hicimos con Vehicle:
var Car = (function () { function Car(model, year, engineSize, wheelCount) { this.wheelCount = wheelCount; } return Car; }());
Para implementar la herencia, debemos especificar en la IIFE la clase de la que se va a heredar así como también agregar un parámetro a la IIFE donde se recibirá a la instancia de la clase padre:
var Car = (function (parent) { function Car(model, year, engineSize, wheelCount) { parent.call(this, model, year, engineSize); this.wheelCount = wheelCount; } return Car; }(Vehicle));
Para terminar la implementación debemos modificar el prototipo de la clase Car, el constructor y mandar a llamar al constructor de la clase base:
var Car = (function (parent) { Car.prototype = new Vehicle(); Car.prototype.constructor = Car; function Car(model, year, engineSize, wheelCount) { parent.call(this, model, year, engineSize); this.wheelCount = wheelCount; } return Car; }(Vehicle));
Vamos crear la clase Boat con la diferencia de que las propiedades extras que tendrá son width y length:
var Boat = (function (parent) { Boat.prototype = new Vehicle(); Boat.prototype.constructor = Boat; function Boat(model, year, engineSize, width, length) { parent.call(this, model, year, engineSize); this.width = width; this.length = length; } return Boat; }(Vehicle));
Con esto ya logramos que las clases Car y Boat heredaran de Vehicle, sólo falta probar que realmente funcione y esto lo haremos con otra IIFE:
(function () { var aCar = new Car('Chevy', 2000, 1.6, 4); alert('Car isStarted: ' + aCar.isStarted()); aCar.start(); alert('Car isStarted: ' + aCar.isStarted()); alert('Model: ' + aCar.model + '\nYear: ' + aCar.year + '\nEngine size: ' + aCar.engineSize + '\nWheel count: ' + aCar.wheelCount); var aBoat = new Boat('SL350', 2008, 0.7, 2, 3); alert('Boat isStarted: ' + aBoat.isStarted()); aBoat.start(); alert('Boat isStarted: ' + aBoat.isStarted()); alert('Model: ' + aBoat.model + '\nYear: ' + aBoat.year + '\nEngine size: ' + aBoat.engineSize + '\nWidth: ' + aBoat.width + '\nLength: ' + aBoat.length); }());
Cuando ejecutemos nuestro código veremos que la primera alerta nos dirá que el carro no está encendido, después de mandar a llamar a la función start() y volver a verificar el estado del carro, veremos que la alerta dirá que el carro ya está encendido.    Al mostrar la información con la tercera alerta, veremos que las propiedades fueron correctamente asignadas.

Con la parte del código que prueba la clase Boat el resultado será similar, a diferencia que mostrará Width y Length que son propias de la clase Boat.

¿Cosas que se pueden lograr con esto?, es difícil decirlo porque dependerá mucho de lo que quieres hacer y hasta dónde quieres llegar, pero seguro que ya tendrás una herramienta más para mejorar tu codificación.

Espero te haya resultado útil la entrada, nos vemos en la siguiente.

miércoles, 12 de junio de 2013

WCF REST Service con JQuery



En esta nueva entrada veremos la forma de exponer un servicio WCF a través de un endpoint que soportará peticiones REST desde un cliente jquery.

¿Por qué un servicio WCF?, recordemos de la implementación del servicio está totalmente separada de la forma de comunicación, esto significa que podremos utilizar la misma implementación para exponer nuestra información a clientes de otras plataformas o a computadoras o servidores que se encuentren en la red interna de nuestro host.    Todo esto sin tener que estar generando nuevas implementaciones.

Es importante notar que utilizaremos la base de datos AdventureWorks, en caso de que no la tengas la puedes obtener de la siguiente dirección: http://msftdbprodsamples.codeplex.com/releases/view/93587

Abriremos el Visual Studio 2010 ejecutándolo como administrador (para evitar el uso de netsh para apartar el puerto) y comenzaremos por crear una solución en blanco llamada WCFJQuery, en esta solución vamos a agregar una biblioteca de clases y la llamaremos AWDB.

AWDB.

En este proyecto vamos a eliminar el archivo Class1.cs que trae por defecto y vamos a agregar un nuevo elemento de tipo ADO.NET Entity Data Model.    Si ya sabes cómo crear un modelo entonces sáltate hasta la creación del siguiente proyecto, la indicación es crear el modelo llamado AW con Production.ProductSubcategory y Production.Product con las entidades pluralizadas, de lo contrario sigue conmigo.

Al nuevo elemento de tipo ADO.NET Entity Data Model vamos a llamarlo AW.edmx, en el asistente elegimos la opción "Generate from database", vamos a crear la conexión hacia AdventureWorks utilizando autenticación de Windows dando clic en el botón "New connection", escribimos el nombre del servidor y elegimos la base de datos AdventureWorks de la lista desplegable, damos clic en "Test connection" y deberá salir un mensaje como el que se ve en la imagen.


Damos clic en Ok para cerrar el mensaje que dice que la conexión fue exitosa y damos clic en Ok para crear la conexión, el nombre de la cadena de conexión será AdventureWorksEntities.    Al dar clic en siguiente nos mostrará la lista de objetos utilizables y deberemos elegir las tablas Production.ProductSubcategory y Production.Product, elegimos la opción "Pluralize or singularize ..." y al modelo lo nombraremos AWModel; la configuración deberá quedar como se ve en la siguiente imagen:


Damos clic en finalizar y ya tenemos nuestro modelo terminado.

AWServices.

Vamos a agregar un proyecto de tipo biblioteca de clases y lo llamaremos AWServices, eliminamos el archivo Class1.cs que trae por defecto.    Agregamos referencia al proyecto AWDB, también tenemos que agregar referencia a System.Data.Entity y System.ServiceModel.Web.

También agregamos un elemento de tipo WCF Service llamado ProductsCatalog y una clase llamada Product que tenga las propiedades públicas Id (int), Name (string) y ListPrice (decimal) siendo todas de lectura-escritura, recuerda hacerla pública, por defecto al agregar una clase la declara como privada.

Ahora agregamos otra clase que se llame Subcategory que tendrá las propiedades públicas Id (int) y Name (string) siendo de lectura-escritura, recuerda hacerla pública.

En IProductsCatalog vamos a eliminar la operación DoWork que aparece por defecto y vamos a agregar una nueva que nos permitirá obtener los productos que pertenezcan a una subcategoría en particular con el siguiente código:

[OperationContract]
List<Product> GetProducts(int subcategoryId);

También vamos a agregar otra operación que nos devolverá la lista de subcategorías que estén registradas en la base de datos con el siguiente código:

[OperationContract]
List<Subcategory> GetSubcategories();

Ya que tenemos el contrato de nuestro servicio, ahora vamos a crear la implementación del mismo en el archivo ProductsCatalog.cs.

Eliminamos el procedimiento DoWork que trae por defecto, damos clic derecho en la interfaz que implementa y elegimos Implement Interface - Implement Interface, esto nos creará los stub necesarios para la implementación de las funciones que tiene la interfaz.

En la función GetSubcategories vamos a proyectar las subcategorías que están en la base de datos hacia una lista de objetos de tipo Subcategory con el siguiente código:

public List<Subcategory> GetSubcategories()
{
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
    using (AWDB.AdventureWorksEntities db = new AWDB.AdventureWorksEntities())
    {
        return db.ProductSubcategories.Select(sc => new Subcategory()
        {
            Id = sc.ProductSubcategoryID,
            Name = sc.Name
        }).ToList();
    }
}

En la función GetProducts buscaremos a los productos que pertenecen a la subcategoría cuyo identificador recibimos como parámetro y los proyectamos hacia una lista de objetos de tipo Product con el siguiente código:

public List<Product> GetProducts(int subcategoryId)
{
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
    using (AWDB.AdventureWorksEntities db = new AWDB.AdventureWorksEntities())
    {
        return db.Products.Where(p => p.ProductSubcategoryID == subcategoryId).Select(p => new Product()
        {
            Id = p.ProductID,
            Name = p.Name,
            ListPrice = p.ListPrice
        }).ToList();
    }
}

En ambas operaciones encontraremos una línea que agrega una cabecera Access-Control-Allow-Origin con un valor "*", esto es para que pueda ser utilizado desde cualquier dominio.

Hasta ahora nuestro servicio ya está listo para ser alojado y utilizado, pero debemos hacerle unas pequeñas modificaciones o ajustes a la implementación para que soporte REST.    Vamos a agregar el espacio de nombres System.ServiceModel.Web en la implementación del servicio y vamos a agregar el siguiente atributo a la función GetSubcategories:

[WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "Subcategories")]
public List<Subcategory> GetSubcategories()

Con esta configuración el mensaje que se devolverá a los clientes que realicen el request a la dirección Subcategories estará en formato JSON con lo que podremos utilizarlo de forma muy sencilla desde javascript.

También vamos a ajustar la función GetProducts para que reciba solicitudes utilizando REST:

[WebGet(ResponseFormat = WebMessageFormat.Json, UriTemplate = "Products?scid={subcategoryId}")]
public List<Product> GetProducts(int subcategoryId)

En esta configuración podemos notar que estamos agregando una variable por query string que se llamará scid y cuyo valor estará asignado al parámetro subcategoryId que solicita nuestra función.
Compila la solución y corrije cualquier detalle que aparezca en la lista de errores, si seguiste al pie de la letra lo que hemos hecho hasta ahora no deberás tener problema alguno.

AWHost.

Vamos a agregar un proyecto de tipo Aplicación de Consola y lo llamaremos AWHost, en este proyecto se estará alojando nuestro servicio AWServices.

Vamos a agregar referencia a System.ServiceModel y al proyecto AWServices.    Agregamos un nuevo elemento de tipo Application Configuration File y le dejaremos el nombre por defecto que mostrará: app.config, debemos abrir este archivo y pegar la cadena de conexión que tenemos en el proyecto AWDB en su archivo app.config, posteriormente registraremos nuestro servicio ProductsCatalog utilizando el siguiente código:

<system.serviceModel>
    <behaviors>
        <endpointBehaviors>
            <behavior name="HelpEnabled">
                <webHttp helpEnabled="true"/>
            </behavior>
        </endpointBehaviors>
    </behaviors>
    <services>
        <service name="AWServices.ProductsCatalog">
            <endpoint address="" binding="webHttpBinding" contract="AWServices.IProductsCatalog" behaviorConfiguration="HelpEnabled"></endpoint>
            <host>
                <baseAddresses>
                    <add baseAddress="http://JorgeToriz-PC:8001/AWServices"/>
                </baseAddresses>
            </host>
        </service>
    </services>
</system.serviceModel>

Deberás modificar el atributo baseAddress para que haga referencia al nombre de tu equipo, en el código que estoy poniendo hace referencia a mi equipo.

El behavior HelpEnabled permitirá que la dirección http://JorgeToriz-PC:8001/AWServices/help exponga todos los servicios que están a la disposición, en esta descripción aparecerá la forma en que deben llamarse y también los resultados que devolverán las funciones.

En el proyecto necesitamos referencia a System.ServiceModel.Web para poder alojar este servicio REST, para poder hacerlo necesitamos que el proyecto utilice el .NET Framework 4 y no el .NET Framework 4 Client Profile.    Para cambiar esta configuración abriremos las propiedades del proyecto y en el tab Application buscaremos la opción "Target Framework", de la lista desplegable deberemos elegir ".NET Framework 4", el IDE nos mostrará un mensaje de aviso, vamos a darle que "Sí" para que realice los cambios necesarios.

Ahora sí, vamos a agregar referencia a System.ServiceModel.Web y vamos a abrir nuestro archivo Program.cs, en él agregaremos el espacio de nombres System.ServiceModel.Web y escribiremos el siguiente código para poner disponible nuestro servicio:

static void Main(string[] args)
{
    WebServiceHost host = new WebServiceHost(typeof(AWServices.ProductsCatalog));
    host.Open();
    Console.WriteLine("El servicio ya está en ejecución, presiona cualquier tecla para detenerlo");
    Console.ReadKey();
    host.Close();
}

Al ejecutar el proyecto (sin debug) nos deberá mostrar una pantalla como esta:


Si visitamos la URL http://JorgeToriz-PC:8001/AWServices/help (cambiando JorgeToriz-PC por el nombre de tu equipo) deberá mostrar el navegador nuestra lista funciones como en la siguiente imagen:


Si damos clic en Products o Subcategories nos mostará la información que devuelve cada una de las URL.

AWClient.

Vamos a agregar un sitio web vacío a nuestra solución al que llamaremos AWClient, en ese proyecto crearemos una carpeta llamada js donde meteremos el script de jquery, si no lo tienes puedes descargar jquery de esta liga (http://code.jquery.com/jquery-1.10.1.min.js).

Dentro de nuestro sitio web vamos a agregar una página HTML a la que llamaremos ProductsCatalog, enlla agregaremos referencia a jquery y también agregaremos el siguiente código que hace llamadas utilizando REST para poder obtener el catálogo de productos:

<table>
    <tr>
        <td>Subcategories</td>
        <td>
            <select id="ddlSubcategories" onchange="return loadProducts()"></select>
            <script language="javascript" type="text/javascript">
                function loadProducts() {
                    $.ajax({
                        url: 'http://JorgeToriz-PC:8001/AWServices/Products?scid=' + $('#ddlSubcategories').val(),
                        type: 'GET',
                        success: loadProducts_success
                    });
                    return false;
                }
                function loadProducts_success(d) {
                    var products = '<table border="1"><tr><td>Id</td><td>Name</td><td>List price</td></tr>';
                    for (var i = 0; i < d.length; i++) {
                        products += '<tr>';
                        products += '<td>' + d[i].Id + '</td>';
                        products += '<td>' + d[i].Name + '</td>';
                        products += '<td>' + d[i].ListPrice + '</td>';
                        products += '</tr>';
                    }
                    products += '</table';
                    $('#divProducts').html(products);
                }
                function loadSubcategories() {
                    $.ajax({
                        url: 'http://JorgeToriz-PC:8001/AWServices/Subcategories',
                        type: 'GET',
                        success: loadSubcategories_success
                    });
                }
                function loadSubcategories_success(d) {
                    var subcategories = $('#ddlSubcategories');
                    for (var i = 0; i < d.length; i++) {
                        $('<option value="' + d[i].Id + '">' + d[i].Name + '</option>').appendTo(subcategories);
                    }
                    loadProducts();
                    subcategories.focus();
                }
                $(document).ready(loadSubcategories);
            </script>
        </td>
    </tr>
</table>
<div id="divProducts"></div>

Resumen.

Como podemos ver no es tan complicado el exponer un servicio WCF para ser utilizado como REST desde javascript, un punto fino es permitir las llamadas desde otros dominios utilizando la cabecera Access-Control-Allow-Origin en el WebOperationContext actual, mientras que el otro punto importante es elegir bien la estructura de las direcciones para REST y configurarlas en la implementación del servicio utilizando el atributo WebGet.

Espero te haya resultado de utilidad esta entrada.

jueves, 24 de mayo de 2012

Easing Functions



El trabajo ha comido bastante mi cerebro y había estado llegando muy cansado a la casa, es por eso que no había podido publicar algo reciente, pero hoy que tengo unos minutos libres quise escribir una entrada que aborde una de las características de jquery que más me gusta, las funciones easing.

Como ya sabemos, jquery agrega una función llamada animate a cualquier objeto jquery.   Vayamos construyendo un ejemplo sencillo:

1. Creamos un nuevo sitio web (al mío lo llamé EasingFunctions) en Visual Studio.
2. Creamos la carpeta js y ahí metemos la librería jquery y el plugin easing de jquery.
3. Agregamos una página HTML y la llamaremos TestFunctions.htm
4. Agregamos referencia a jquery.js y a jquery.easing.1.3.js.    Aquí es importante recordar que HTML se va intepretando línea a línea por lo que es necesario que la primera referencia sea hacia jquery y la segunda sea al plugin de funciones.

<script language="javascript" type="text/javascript" src="js/jquery.js"></script>
<script language="javascript" type="text/javascript" src="js/jquery.easing.1.3.js"></script>

5. Busquemos algunas imágenes para hacer un pequeño slider en nuestra página y así vayamos viendo cómo es que se comportan algunas funciones del plugin.    Vayan a Google y busquen 5 imágenes de lo que quieran (yo voy a utilizar de los Simpson) que sean de 400x300 y las nombraremos 1.jpg, 2.jpg, 3.jpg, 4.jpg y 5.jpg y las guardaremos en la carpeta img de nuestro proyecto.

Hasta ahora nuestro explorador de soluciones debería verse así:



6. Vamos a construir el código HTML para meter botones de "adelante" y "atrás" y ver la galería fotográfica.   Notemos que el secreto, al menos en la solución que yo propongo, es colocar un div con un overflow en hidden para que los elementos que estén más allá de las dimensiones del div estén ocultos.

<table cellspacing="0" cellpadding="0">
    <tr>
        <td align="left"><input type="button" value="Anterior" /></td>
        <td align="right"><input type="button" value="Posterior" /></td>
    </tr>
    <tr>
        <td colspan="2">
            <div style="width: 400px; height: 300px; overflow: hidden; position: relative;">
                <table cellspacing="0" cellpadding="0" style="position: absolute; left: 0px; top: 0px;">
                    <tr>
                        <td><img src="img/1.jpg" /></td>
                        <td><img src="img/2.jpg" /></td>
                        <td><img src="img/3.jpg" /></td>
                        <td><img src="img/4.jpg" /></td>
                        <td><img src="img/5.jpg" /></td>
                    </tr>
                </table>
            </div>
        </td>
    </tr>
</table>

De una vez he puesto las imágenes en un arreglo horizontal en una tabla.    También deberemos notar que el div tiene un posicionamiento relativo de manera tal que los elementos que contenga estén posicionados respecto al div y no respecto al documento completo, tal y como lo estamos haciendo con la tabla que de inicio estará en la coordenada (0,0) del div.

Ahora crearemos un script sumamente básico que lo que hará es mover la tabla de manera tal que se vea el cambio de imágenes.   Modificaremos la declaración de nuestros botones y quedarán de la siguiente manera:

<input type="button" value="Anterior" onclick="showPrevious()" />
<input type="button" value="Posterior" onclick="showNext()" />

Y el script lo que hará es mover la tabla hacia la izquierda o la derecha dependiendo del botón que hayan presionado:

<script language="javascript" type="text/javascript">
    var currentPic = 0;
    var picCount = 5;
    var picWidth = 400;
    function showPrevious() {
        if (currentPic == 0)
            currentPic = picCount - 1;
        else
            currentPic--;
        $('#tblPics').css('left', -currentPic * picWidth);
    }
    function showNext() {
        currentPic = (currentPic + 1) % picCount;
        $('#tblPics').css('left', -currentPic * picWidth);
    }
</script>

He declarado algunas variables que guarden los valores importantes de manera tal que después podamos agregar fotos sin problema alguno.

Ejecutemos nuestra página y veamos cómo van cambiando las fotografías dependiendo del botón que hayamos presionado.

Con un simple cambio en el código, ahora lo que haremos es hacer el movimiento de la tabla de forma animada.    Utilizaremos la función animate y nuestro código deberá quedar de la siguiente manera:

Antes: $('#tblPics').css('left', -currentPic * picWidth);
Ahora: $('#tblPics').animate({ 'left': -currentPic * picWidth });

Probemos nuestra página y veremos que el cambio de foto a foto ya se ve mucho mejor.

El parámetro que le estamos pasando a la función animate, es un objeto que contiene todos aquellos atributos de la hoja de estilo de nuestra tabla que queramos modificar de forma animada.

De inicio, jquery utiliza una función de animación llamada "linear", esto significa que no tiene aceleración.    Modifiquemos nuestra llamada a la función animate hacia el segundo valor que por defecto trae jquery:

Antes: $('#tblPics').animate({ 'left': -currentPic * picWidth });
Ahora: $('#tblPics').animate({ 'left': -currentPic * picWidth }, 'normal', 'swing');

Si ejecutamos de nuevo la página no veremos un cambio muy significativo dado que el ancho de las fotografías es algo pequeño, pero ahora que utilicemos las funciones que tiene el plugin podremos observar grandes cambios.

Probemos la animación "easeInCubic":

$('#tblPics').animate({ 'left': -currentPic * picWidth }, 'normal', 'easeInCubic');

Al ejecutar la página veremos que al principio comienza lento y conforme va pasando el tiempo comienza a acelerar.    En lo personal, me gusta mucho la función easeInCirc.

Para obtener la lista completa de las funciones a las que tenemos acceso a través de este plugin, abrimos el archivo jquery.easing.1.3.js y ahí aparecerán todas las funciones easing a las que se les ha asignado un nombre en el plugin.

Ya dependerá del gusto de nuestro cliente cuál utilizaremos, pero en las funciones easing tenemos un aliado que nos permitirá darle una vida y presentación diferente a nuestras páginas.

Podrán ver este pequeño ejercicio en ejecución en esta dirección web:

http://www.jorgetoriz.com/easingfunctions/testfunctions.htm

Espero les haya sido de mucha utilidad.

viernes, 30 de marzo de 2012

JSON 2



En esta segunda entrada veremos cómo podemos enviar un objeto de forma asíncrona utilizando JSON.

Tomando el ejercicio anterior como preámbulo, utilizaremos el mismo objeto Product para realizar el envío de información.

El primer problema que podemos encontrar es que la notación JSON no es tan fácil de generar (no es algo del otro mundo pero definitivamente quita mucho tiempo).    Para facilitarnos el trabajo existe una librería llamada json.js que podemos descargar de aquí.

Esta librería expone una función llamada stringify que nos ayudará a traducir un objeto javascript a su notación JSON correspondiente.

Vamos a modificar nuestro handler AddProduct.ashx para que reciba el objeto y simule meterlo a una base de datos.    Primero debemos agregar referencia al espacio de nombres donde está definido el objeto JavaScriptSerializer:

using System.Web.Script.Serialization:

En el procedimiento ProcessRequest vamos a recibir el objeto JSON y lo vamos a "deserializar" al objeto Producto que tenemos en nuestra página web.

public void ProcessRequest(HttpContext context)
{
    string jsonProduct = context.Server.UrlDecode(Uri.UnescapeDataString(context.Request.Form.ToString()));
    JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
    DataObjects.Product product = jsSerializer.Deserialize<DataObjects.Product>(jsonProduct);
    System.Threading.Thread.Sleep(2000);
}

En la primera línea de código estamos primero decodificando con la función UnescapeDataString para quitar todos los caracteres que comienzan con "%", por ejemplo %20 que es el espacio en blanco; después estamos utilizando la función UrlDecode para cambiar todos los "+" por espacios en blanco.    Al final de estas dos funciones obtenemos el código JSON que nos fue enviado desde el cliente.

Con la tercera línea de código estamos tomando el código JSON y lo estamos convirtiendo en el objeto Product que le corresponde en nuestro proyecto.    Las características del objeto Product permiten realizar esta operación (propiedades públicas de lectura y escritura cuyos nombres coinciden con las propiedades del objeto JSON).

Finalmente estamos llamando a la función Sleep para simular que estamos llevando a cabo un proceso de registro.

Ahora vamos a ajustar nuestro archivo Products.htm para que haga la llamada al servidor.   Primero agregaremos una referencia a la librería json.js:

<script type="text/javascript" language="javascript" src="js/json.js"></script>

Después agregaremos un botón para ejecutar la función que enviará el objeto al servidor:

<input type="button" value="Send product" onclick="return sendProduct()" />

Y finalmente crearemos nuestro function sendProduct que será la encargada de realizar el POST al servidor:

function sendProduct() {
    var myProduct = new Product(1, 'Product 1');
    $.ajax({
        'type': 'POST',
        'dataType': 'json',
        'data': JSON.stringify(myProduct),
        'url': '/Handlers/AddProduct.ashx',
        'success': sendProduct_success,
        'error': sendProduct_error
    });
}
function Product(id, name) {
    this.Id = id;
    this.Name = name;
}
function sendProduct_success(d) {
    alert('El producto ha sido registrado');
}
function sendProduct_error(d) {
    alert('Mmm... algo anda mal');
}

Podemos darnos cuenta que:
1. He creado un objeto personalizado llamado "Product" que está definido de igual forma que el que tenemos en DataObjects.Product en el servidor, esto es para evitar problemas de serialización/deserialización.
2. El tipo de la llamada es POST.
3. Estamos especificando que el tipo de información que estamos enviando es json.
4. Utilizamos la función JSON.stringify para traducir nuestr objeto myProduct a JSON.

Espero les haya sido de mucha utilidad.

sábado, 24 de marzo de 2012

JSON



Cuando queremos implementar un modelo de comunicación asíncrono entre una página web y el servidor tenemos al menos dos opciones: JSON y XML a través de requests con distintos verbos dependiendo del objetivo de la llamada o ejecución.    Aún cuando sea algo muy sabido creo que es necesario mecionar que generalmente los verbos se utilizan de la siguiente forma:

GET: Leer
POST: Agregar / Actualizar
DELETE: Eliminar
PUT: Agregar / Actualizar

En este ejercicio estaremos ocupando los dos verbos más comunes que son el GET y el POST.    Para preparar el entorno agregaremos los siguientes elementos:

1. Crear carpeta js.
2. Agregar jquery.js a la carpeta js.
3. Crear carpeta Handlers.
4. Agregar Products.ashx y AddProduct.ashx a la carpeta Handlers.
5. Crear carpeta DataObjects.
6. Agregar clase Product a la carpeta DataObjects.
7. Agregar archivo Products.html al proyecto (opté por un HTML para dejar en claro que todo se hará a través de requests asíncronos y no a través de posts de una WebForm)

Si vemos el explorador de soluciones (Ctrl + Alt + L) veremos un arreglo de archivos como este:


Vamos a agregarle dos propiedades públicas de lectura y escritura a nuestra clase Product:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Ahora vamos a crear el código necesario para que Products.ashx devuelva la lista de productos en JSON.    Para empezar vamos a agregar dos espacios de nombres, el que contiene a JavaScriptSerializer y a nuestra clase Product:

using System.Web.Script.Serialization;
using SendReceiveJSON.DataObjects;

Los handlers reciben las peticiones a través del procedimiento ProcessRequest.   Notemos que por defecto ya viene un código que dice el tipo MIME que se va a devolver y el contenido de la respuesta.    El tipo MIME de JSON es "application/json" por lo que deberemos fijar este valor en la propiedad ContentType de la propiedad Response.

context.Response.ContentType = "application/json";

Ahora vamos a llenar una lista de objetos Product y la vamos a convertir en JSON utilizando el objeto JavaScriptSerializer:

List<Product> result = new List<Product>();
for (int i = 0; i < 10; i++)
{
    result.Add(new Product() { Id = i + 1, Name = "Product " + (i + 1).ToString() });
}
JavaScriptSerializer js = new JavaScriptSerializer();
context.Response.Output.Write(js.Serialize(result));

Si ejecutamos directamente el handler Products.ashx desde el navegador deberemos ver un resultado como este:


Para terminar este primer ejercicio vamos a crear el código necesario en nuestra página Products.htm para que llame al handler y muestre el listado de productos.    Para esto necesitamos un botón y un contenedor donde insertaremos una tabla con los productos.

<input type="button" value="Get products" onclick="return getProducts()" />
<hr />
<div id="divProducts"></div>

En nuestra función getProducts haremos la llamada a Products.ashx (recuerden agregar el script jquery en el head):

<script language="javascript" type="text/javascript">
    function getProducts() {
        $.ajax({
            'type': 'GET',
            'url': '/Handlers/Products.ashx',
            'success': getProducts_success,
            'error': getProducts_error
        });
        return false;
    }
    function getProducts_success(d) {
    }
    function getProducts_error(d) {
    }
</script>

Con la función $.ajax (de jquery) podemos realizar peticiones asíncronas (con el verbo type) al recurso web identificado determinado a través de la propiedad url.    Cuando la ejecución es exitosa se mandará a llamar a la función getProducts_success y cuando haya un error se ejecutará la función getProducts_error.

Vamos a modificar las funciones getProducts_success y getProducts_error para darle significado al ejercicio:

function getProducts_success(d) {
    var products = '<table cellspacing="0" cellpadding="5">';
    products += '<tr><th>Id</th><th>Name</th></tr>';
    for (var i = 0; i < d.length; i++) {
        products += '<tr>';
        products += '<td>' + d[i].Id + '</td>';
        products += '<td>' + d[i].Name + '</td>';
        products += '</tr>';
    }
    products += '</table>';
    products = $(products);
    $('#divProducts').append(products);
}
function getProducts_error(d) {
    alert('There was an error during execution process');
}

Notemos que estamos ocupando las propiedades públicas de nuestro objeto Product (Id y Name), esto se debe a que jquery realiza la decodificación de JSON y genera el arreglo de objetos necesarios para mostrar la información.    Es por eso que se llama JSON (JavaScript Object Notation)

En la siguiente entrada veremos cómo enviar un objeto Product al servidor a través de un POST en notación JSON.

miércoles, 21 de marzo de 2012

Mensaje Javascript 2



En esta segunda entrada referente a cómo mostrar un mensaje al usuario de un sitio web, le agregaremos tres parámetros a nuestra función showMessage:

1. Tipo de mensaje, el cual determinará el color de fondo del contenedor del mensaje.
2. Tiempo, será la cantidad de milisegundos que el mensaje deberá estar visible.
3. Callback, será una referencia a una función que se desee ejecutar después de que el mensaje se destruya.

Tal como en el ejercicio anterior, iremos cubriendo uno a uno de los elementos hasta tener el script completo.

Para manejar un tipo de mensaje, vamos a agregar un parámetro msgType que podrá tener como valores: 'error', 'success' y habrá un color default para cuando no se reciba este parámetro.

function showMessage(msg, msgType)

Ahora modificaremos un poco el código que establece el estilo de nuestra variable message y esto se debe a que el color de fondo dependerá del tipo de mensaje:

message.css({
    'height': '20px',
    'width': '100%',
    'position': 'fixed',
    'top': '-28px',
    'left': '0px',
    'margin': '0px',
    'background-color':
        msgType == 'error' ? '#d83a28' : (msgType == 'success' ? '#5aa250' : '#5f8be3'),
    'color': '#FFFFFF',
    'font-size': '15px',
    'text-align': 'center',
    'padding-top': '4px',
    'padding-bottom': '4px',
    'font-family': 'Verdana'
}).text(msg);

Se diría que le estamos asignando el color de una forma poco elegante, pero podríamos hacerlo un poco mejor si creamos una función llamada getMsgColor que reciba como parámetro el tipo de mensaje a mostrarse y nos devuelva el código del color.    Con este cambio el código quedaría de la siguiente manera:

function getMsgColor(msgType) {
    switch (msgType) {
        case 'error': return '#d83a28';
        case 'success': return '#5aa250';
        default: '#5f8be3';
    }
}

function showMessage(msg, msgType) {
    var message = $('<div></div>');
    message.css({
        'height': '20px',
        'width': '100%',
        'position': 'fixed',
        'top': '-28px',
        'left': '0px',
        'margin': '0px',
        'background-color': getMsgColor(msgType),
        'color': '#FFFFFF',
        'font-size': '15px',
        'text-align': 'center',
        'padding-top': '4px',
        'padding-bottom': '4px',
        'font-family': 'Verdana'
    }).text(msg);
    ... aquí continúa la función showMessage

La ventaja es que el código queda mucho más limpio y será muy fácil el agregar un nuevo tipo de mensaje con su color respectivo.    Probemos nuestra nueva función con el botón que ya tenemos (del ejercicio anterior):

<input type="button" value="Show message" onclick="return showMessage('Código y datos', 'success')" />

Para cubrir el segundo punto agregaremos otro parámetro llamado timeout que determinará el tiempo (en milisegundos) que deberá mostrarse el mensaje:

function showMessage(msg, msgType, timeout)

Y antes de mostrar nuestor mensaje verificaremos si recibimos un valor para nuestro parámetro timeout o bien le asignamos el tiempo default:

if (!timeout) timeout = 3500;
message.animate(
    { 'top': '0px' },
    'normal',
    null,
    function() {
        window.setTimeout(function() {
            message.animate({ 'top': '-28px' }, 'normal', null, function() { message.remove(); });
        }, timeout);
    }
);

Finalmente también nuestra función queremos que pueda mandar a llamar a una función determinada por el usuario para cuando haya sido destruído el mensaje.    Vamos a agregar un parámetro llamado callback:

function showMessage(msg, msgType, timeout, callback)

Al final el código de nuestro ejercicio debería quedar como el que a continuación coloco:

<input type="button" value="Show message" onclick="return showMessage('Código y datos', 'success', 2000, sayHello)" />
<script type="text/javascript">
    function getMsgColor(msgType) {
        switch (msgType) {
            case 'error': return '#d83a28';
            case 'success': return '#5aa250';
            default: return '#5f8be3';
        }
    }
    function showMessage(msg, msgType, timeout, callback) {
        var message = $('<div></div>');
        message.css({
            'height': '20px',
            'width': '100%',
            'position': 'fixed',
            'top': '-28px',
            'left': '0px',
            'margin': '0px',
            'background-color': getMsgColor(msgType),
            'color': '#FFFFFF',
            'font-size': '15px',
            'text-align': 'center',
            'padding-top': '4px',
            'padding-bottom': '4px',
            'font-family': 'Verdana'
        }).text(msg);
        $(document.body).append(message);
        if (!timeout) timeout = 3500;
        message.animate(
            { 'top': '0px' },
            'normal',
            null,
            function() {
                window.setTimeout(function() {
                    message.animate({ 'top': '-28px' }, 'normal', null, function() { message.remove(); if (callback) callback(); });
                }, timeout);
            }
        );
    }
    function sayHello() {
        alert('Hola mundo!');
    }
</script>

Espero les sea de mucha utilidad.

jueves, 15 de marzo de 2012

Mensaje Javascript



Un problema o dilema común en los sitios web, es el decidir la estrategia para mostrar mensajes al usuario, ya sean informativos, correctivos, de alarma, etc.

La función alert de javascript se ha convertido en un dolor de cabeza ya que afecta la navegabilidad de nuestro sitio, aparte hay que tomar en cuenta que el icono del diálogo muestra algo que le puede dar a entender al usuario que algo malo está pasando.

A mí me ha gustado mucho utilizar una alternativa que me permite deslizar un mensaje en la parte superior y que después de un tiempo éste desaparece de forma automática.    No impide la navegabilidad del sitio, no es agresivo y es muy sencillo de programar.

Comencemos por colocar un elemento DIV en la parte superior de un documento HTML (con referencia a jquery) en blanco:

<div style="height: 20px; width: 100%; position: fixed; top: 0px; left: 0px; margin: 0px; background-color: #264182; color: #FFFFFF; font-size: 15px; text-align: center; padding-top: 4px; padding-bottom: 4px; font-family: Verdana;">Código y Datos</div>

Tenemos un contenedor de 20px de alto con un posicionamiento de tipo fixed que nos permitirá mostrar el mensaje sin importar la posición del scroll en el navegador del visitante.   Le hemos dado un padding superior e inferior para centrar verticalmente el mensaje.

Ahora vamos a agregar un botón que nos permita experimentar un poco con jquery para ir construyendo el código necesario para mostrar nuestro mensaje.

<br /><br /><input type="button" value="Hide message" onclick="return hideMessage()" />

He colocado unos saltos de línea para que el mensaje no tape a nuestro botón.

Ahora lo que haremos es una pequeña animación que moverá el mensaje hacia arriba hasta que desaparezca:

<script type="text/javascript">
    function hideMessage() {
        $('#message').animate({
            'top': -28
        });
        return false;
    }
</script>

Veamos que estamos moviendo 28px y no 20px, esto se debe a que tenemos que sumar los 4px del padding superior y los 4px del padding inferior que actualmente tiene nuestro elemento DIV.

Visitemos nuestra página y veremos que ya se oculta el mensaje.

Ahora agregaremos otro botón para mostrar el mensaje:

<input type="button" value="Display message" onclick="return displayMessage()" />

Y agregaremos su función (dentro del bloque <script>) javascript correspondiente:

function displayMessage() {
    $('#message').animate({
        'top': 0
    });
    return false;
}

Si volvemos a ejecutar nuestra página de prueba, veremos que ya podemos ocultar el mensaje mediante una animación y también podemos mostrarlo mediante una animación.

Bueno, tenemos las dos funciones básicas para mostrar el mensaje

Ahora generamos otro archivo HTML (con referencia a jquery) donde tendremos nuestro código final y agregamos un botón para mandar a llamar a nuestra función:

<input type="button" value="Show message" onclick="return showMessage('Código y datos')" />

Nuestra función showMessage(msg) deberá:

1. Construir un elemento DIV.
2. Establecer todas las propiedades para su estilo.
3. Ingresar el texto del mensaje en el elemento DIV.
4. Agregar nuestro elemento DIV al documento.
5. Animar el elemento DIV para que se muestre, después de 3.5 segundos animar el elemento DIV para ocultarlo y descargarlo del DOM (destruirlo)

Iremos construyéndola paso a paso para hacer claro el ejemplo:

<script type="text/javascript">
function showMessage(msg) {
//1.
    var message = $('<div></div>');

//2. Notar que la propiedad top comienza con un valor de -28px para que no se vea el elemento DIV al momento de inyectarlo al DOM.
    message.css({
        'height': '20px',
        'width': '100%',
        'position': 'fixed',
        'top': '-28px',
        'left': '0px',
        'margin': '0px',
        'background-color': '#264182',
        'color': '#FFFFFF',
        'font-size': '15px',
        'text-align': 'center',
        'padding-top': '4px',
        'padding-bottom': '4px',
        'font-family': 'Verdana'
    });

//3.
    message.text(msg);

//4.
    $(document.body).append(message);

//5.
    message.animate(
        { 'top': '0px' },
        'normal',
        null,
        function() {
            window.setTimeout(function() {
                message.animate({ 'top': '-28px' }, 'normal', null, function() { message.remove(); });
            }, 3500);
        }
    );
</script>

Es probable que el punto 5 resulte un tanto confuso debido a su redacción, pero no es tan complejo como parece:

1. El primer parámetro de la función animate nos permite determinar las propiedades del estilo que queremos modificar
2. El segundo parámetro dicta la velocidad de la animación.
3. El tercer parámetro es el nombre de la función que determina el tipo de animación a aplicar (funciones matemáticas que calculan cómo se moverá el objeto, jquery tiene la animación linear por defecto).
4. El cuarto parámetro es la función que se ejecutará una vez que la animación haya finalizado, a este parámetro se le llama callback.

En el callback de la animación estamos agregando una función que se ejecutará en 3500 milisegundos.

Se podrá notar que volvemos a ocupar la función animate para ocultar el elemento DIV y su callback destruye el objeto message.

En la siguiente entrada le agregaremos algunas opciones a nuestra función para hacerla mucho más llamativa.

miércoles, 14 de marzo de 2012

MultiUpload dinámico



Hay ocasiones en las que no sabemos de forma exacta cuántos archivos va a cargar el usuario a nuestra aplicación web, por ejemplo una galería fotográfica, donde le damos la posibilidad de cargar 1 o más archivos.

El dolor de cabeza radica en "1 o más" dado que lo que menos queremos es que el usuario tenga que estar dando clic en un botón "Agregar" para que se haga un POST al servidor y generemos otro control de tipo FileUpload y así pueda cargar más fotografías.

En esta entrada explicaré de una forma sencilla y sumamente básica cómo podemos generar controles FileUpload del lado del cliente y así evitemos dar viajes sin sentido al servidor y por lo tanto mejorar la experiencia de nuestro usuario.

La herramienta más sencilla (y versátil) que he encontrado para poder modificar la página desde el navegador del cliente es jquery, por lo que agregaremos el siguiente elemento script en el head de nuestra página Default.aspx (suponiendo que ya cuentan con él y lo han colocado en la carpeta js de nuestro proyecto Web):

<script language="javascript" type="text/javascript" src="js/jquery.js"></script>

Y colocaremos la siguiente interfaz:

<table>
    <tr>
        <td colspan="2"><input type="button" value="Add" onclick="return addUploadField()" /></td>
    </tr>
    <tr>
        <td><input type="file" name="file0" /></td>
        <td></td>
    </tr>
    <tr id="trUploadRow">
        <td colspan="2" align="right"><asp:Button ID="cmdUpload" runat="server" Text="Upload" OnClick="cmdUpload_Click" /></td>
    </tr>
</table>

Tenemos un botón "Add" que nos servirá para agregar un nuevo control de carga de archivos, en las filas siguientes se agregarán los controles de carga de archivos junto con un botón para eliminar el control (si es que el usuario ya no quiere cargar ese archivo) y un botón ASP .NET que ejecutará el POST de la página para la carga de los archivos.

Notemos que es muy importante que nuestros controles de carga de archivos (input de tipo file) deben de contar con el atributo "name" para que éstos sean reconocidos por la propiedad Files del objeto Request.

Ahora escribiremos la función javascript addUploadField que será la encargada de crear los controles de carga:

<script language="javascript" type="text/javascript">
    var currentIndex = 0;
    function addUploadField() {
        currentIndex++;
        var newRow = '<tr>';
        newRow += '<td><input type="file" name="file' + currentIndex + '" /></td>';
        newRow += '<td><input type="button" value="Remove" onclick="return removeUploadField(this)" /></td>';
        newRow += '</tr>';
        newRow = $(newRow);
        newRow.insertBefore($('#trUploadRow'));
    }
    function removeUploadField(e) {
        $(e.parentNode.parentNode).remove();
    }
</script>

La variable currentIndex me ayudará a darle un nombre único a cada control que se agregue.   En la función addUploadField estoy declarando una variable newRow donde estaré concatenando el código HTML necesario para crear el control de carga y el botón de eliminar.

Al atributo name le estoy asignando un valor único al concatenar la variable currentIndex que se incrementa cada vez que el usuario agregue un nuevo control.

En el botón "Remove" estoy llamando a una función "removeUploadField" que será la encargada de eliminar la fila.

Una vez que tengo el código HTML necesario para representar a mi control de carga y mi botón de eliminar, convertiremos el código en objetos jquery para poder manipularos de una forma mucho más simple.    Lo interesante del objeto jquery es que utilizando su funcion insertBefore lo podemos colocar justo arriba del botón "Upload".

En la función removeUploadField únicamente recibimos como parámetro el botón que está llamando a la función, y dado que HTML es un árbol de nodos, podemos obtener a través de la propiedad "parentNode" al objeto TR que queremos eliminar.

Ahora vayamos al archivo Default.aspx.cs donde escribiremos el código de nuestro manejador del evento Click del botón Upload:

using System.IO;
protected void cmdUpload_Click(object sender, EventArgs e)
{
    foreach (string file in Request.Files)
    {
        if (Request.Files[file].ContentLength > 0)
        {
            if (File.Exists(Server.MapPath("~/files/" + Request.Files[file].FileName)))
                File.Delete(Server.MapPath("~/files/" + Request.Files[file].FileName));
            Request.Files[file].SaveAs(Server.MapPath(("~/files/" + Request.Files[file].FileName)));
        }
    }
}

Si ejecutamos la aplicación veremos que aún cuando la estructura de los objetos necesarios para llevar a cabo la carga de los archivos es la adecuada, éstos no son recibidos de forma correcta.    Pongamos un punto de interrupción en la línea del "foreach" y exploremos la propiedad "ContentType" del objeto Request, veremos que tiene el valor "application/x-www-form-urlencoded" y para que los archivos sean transferidos de forma correcta el ContentType debe ser "multipart/form-data".

Se me ocurren dos maneras de corregir este problema:

1. Modificando la etiqueta form de Default.aspx agregándole el atributo "enctype" con el valor "multipart/form-data" de manera tal que quede así:
<form id="form1" runat="server" enctype="multipart/form-data">

2. En el procedimiento Page_Load verificar el valor de la propiedad ContentType del objeto Request y corregirlo si es necesario:

protected void Page_Load(object sender, EventArgs e)
{
    if (form1.Enctype != "multipart/form-data")
        form1.Enctype = "multipart/form-data";
}

Ocupen la que gusten, las dos opciones resultan en la carga correcta de archivos.

Espero haya sido de mucha utilidad esta entrada y les ayude a arrancar con controles propios y extender su funcionalidad tanto como ustedes gusten.