lunes, 28 de marzo de 2011

[HTML/JS] Module pattern

Muy buenas! Cuando creas un sitio web, es normal que vayas añadiendo cada vez más código javascript en él. Al final, seguramente terminaréis desarollando una mini-api, propia que muchas veces reside en un archivo .js, lleno de funciones. Algo como:

function HazAlgo(id, params, callback)
{
}

function _HazAlgoHelper(domObj, params)
{
}





En este caso tenemos dos funciones, HazAlgo y _HazAlgoHelper. La función _HazAlgoHelper es realmente una función privada, es decir está pensada para ser llamada únicamente dentro de HazAlgo. Pero en Javascript no existe directamente el concepto de función privada así que le ponemos un idicador (que empieze por _) y asumimos que todas las funciones que empiecen por _, son privadas.



Como podemos ver, el código javascript no está muy “organizado”: tenemos todas las funciones juntas y además podemos ver aquellas que no deberíamos. El patrón de módulo existe para solventar esos problemas.



El patrón de módulo



La idea del patrón de módulo (module pattern) en Javascript es simular el concepto de una clase estática con métodos públicos y métodos privados. Lo de clase estática viene porque no quiero ir creando objetos de mi módulo, simplemente quiero tener ahí, las funciones públicas. Pero sin ver las privadas. Para los que no lo sepáis: en javascript no existe el concepto de variable estática.



La idea que hay detrás del patrón de módulo es muy simple, y como casi siempre pasa simple significa también brillante: Se trata de crear un objeto anónimo, cuyos campos públicos sean las funciones públicas. Finalmente se crea una instancia de ese objeto y se asigna al namespace global (eso es, al objeto window).



El código de base sería algo como:




(function(wnd) {
var miModulo = function() {
var _HazAlgoHelper = function(domObj, params) {alert("método privado");};
var _HazAlgo = function(id, params, callback) { _HazAlgoHelper(); alert("método público"}; };

return {
HazAlgo : _HazAlgo
};
}

wnd.m$ = miModulo();
})(window);





Bien… se que el código puede parecer lioso, la primera vez, pero vamos a analizarlo.



Podemos ver que estamos definiendo una función anónima que acepta un parámetro (llamado wnd).



¿Y que hace esa función anónima)? Pues para empezar define una variable miModulo que resulta ser… una función (var miModulo = funcion() { …}).



Y que hará esa función cuando se invoque? Pues tres cosas:




  1. Definir una variable llamada _HazAlgoHelper… que es otra función.


  2. Definir una variable llamada _HazAlgo… que es otra función.


  3. Devolver un objeto anónimo con un campo, llamado HazAlgo. El valor de HazAlgo es el mismo que _HazAlgo (es decir una función).



Con eso tenemos una función (miModulo) que cuando la invoquemos nos devolverá un objeto con un solo campo (llamado HazAlgo). Así, teoricamente:




miModulo.HazAlgo();    // Valido
miModulo._HazAlgo(); // No válido
miModulo._HazAlgoHelper(); // NO válido





Fijaos que la gracia está en que HazAlgo() realmente es lo mismo que _HazAlgo()… y desde _HazAlgo() podemos llamar sin ningún problema a _HazAlgoHelper() que es nuestra función privada. Así la clave es mapear las funciones públicas como campos del objeto anónimo devuelto.



¡Con eso hemos simulado el concepto de funciones privadas!



Pero, miModulo es una variable local, es local a la función anónima que estamos definiendo. Así que todavía nos queda un paso más: Guardar miModulo en alguna propiedad del parámetro wnd. Eso es lo que hacemos con la línea:




wnd.m$ = miModulo();





Invocamos miModulo (con parentesis, pues es una función) y guardamos el resultado (el objeto anónimo con el campo HazAlgo) en la propiedad m$ del parámetro wnd.



Ya casi estamos… Hasta este punto hemos definido simplemente una función anónima. Ahora toca invocarla. Fijaos en un detalle importante: la primera línea empieza con un paréntesis abierto. Con eso, de hecho, estamos invocando nuestra función anónima. Pero nuestra función anónima espera un parámetro (wnd) así que debemos pasárselo. Eso es lo que hacemos en la última línea: le pasamos window. Y porque window? Pues porque window es un namespace global: todo lo que esté en window es global.



Por lo tanto ahora, desde cualquier parte de mi página puedo hacer:




<script>m$.HazAlgo(1,2,3);</script>





Y eso es correcto, mientras que hacer.




<script>m$._HazAlgoHelper(1,2);</script>





Nos dará un error.



Para tener nuestro módulo listo para ser usado, basta con tenerlo en un .js propio e incluirlo en aquellas páginas donde lo necesitemos. ¡Y listos!



Espero que esto os parezca interesante y os ayude a organizar vuestro código javascript!



Un saludo!



PD: Como siempre… un crosspost desde mi blog en geeks.ms!

martes, 15 de marzo de 2011

ASP.NET MVC: Previsualizar imágenes subidas (2)

Buenas! Donde dije digo, digo Diego… Sí, ya sé que dije que el segundo post sería como hacerlo con Ajax, pero bueno… la culpa es de twitter, concretamente de @pablonete con el que hemos empezado a hablar sobre si es posible evitar el guardar la imágen físicamente en el servidor. Hay un mecanismo obvio, que es usar la sesión (guardar el array de bytes que conforman la imágen en la sesión). Pero… hay otra? Pues sí: usar data urls!

Data urls

Lo que mucha gente no conoce es que el formato de URL permite incrustar datos que son interpretados por el navegador como si se los hubiese descargado externamente. P.ej. la siguiente url es válida:

data:image/jpeg;base64,xxxxxxx

donde xxxxxxx es la codificación en base64 de la imágen.

P.ej. eso es totalmente correcto:

<img src=”data:image/jpeg;base64,xxxxxx/>





Veamos como podríamos modificar nuestro proyecto anterior, para usar data urls en lugar de un fichero temporal en el servidor… Fijaos que eso sólo evita guardar el fichero, la imagen debe ser subida al servidor.



Las modificaciones son muy simples, por un lado primero vamos a modificar la acción del controlador, para que quede así:




[HttpPost]
public ActionResult SendImage(HttpPostedFileBase img, string base64, string contenttype)
{
if (base64 != null && contenttype != null && img==null)
{
// Aquí podríamos guardar la imagen (en base64 tenemos los datos)
return View("Step2");
}
var data = new byte[img.ContentLength];
img.InputStream.Read(data, 0, img.ContentLength);
var base64Data = Convert.ToBase64String(data);
ViewBag.ImageData = base64Data;
ViewBag.ImageContentType = img.ContentType;
return View("Index");
}





La acción recibe tres parámetros:




  1. img: El fichero seleccionado (contenido del <input type=”file”).


  2. base64: Codificación en base64 de la imagen que se está previsualizándo.


  3. contenttype: Content-type de la imagen que se está previsualizando.



Por supuesto base64 y contenttype sólo se envían si se está previsualizando una imagen. En caso contrario valen null.



Fijémonos lo que hace la acción, si base64 y contenttype valen null, o bien el usuario ha seleccionado una imagen nueva (img != null):




  1. Obtenemos los datos en base64 de la imagen enviada por el usuario (base64Data).


  2. Pasamos esos datos (junto con el content-type) a la vista, usando el ViewBag.



Y la vista que hace? Pues poca cosa:




<form enctype="multipart/form-data" method="post" action="@Url.Action("SendImage")">
<label for="img">Seleccionar imagen:</label>
<input type="file" name="img" /> <br />
<input type="submit" />

@if (ViewBag.ImageData != null)
{
<img src="data:@ViewBag.ImageContentType;base64,@ViewBag.ImageData" />
<input type="hidden" value="@ViewBag.ImageData" name="base64" />
<input type="hidden" value="@ViewBag.ImageContentType" name="contenttype" />
}
</form>





Tenemos el formulario con el input type=”submit”, y luego si en el ViewBag vienen los datos de la imagen que se debe previsualizar, genera 3 campos más:




  1. Una imagen, cuyo src es una data url (formato data:content-type;base64,[datos en base64])


  2. Dos campos hidden (para guardar el content-type y los datos para mandarlos de vuelta al servidor).



Y listos! Con eso tenemos la previsualización de las imágenes sin necesidad de generar fichero temporal alguno.



Finalmente, tres aclaraciones:




  1. Fijaos que la codificación en base64 se incrusta en la página (en este caso se incrusta dos veces, aunque con un poco de javascript podría incrustarse solo una), por lo que esto puede generar páginas muy grandes si la imagen ocupa mucho.


  2. Si no voy errado, los navegadores sólo están obligados a soportar URLs de hasta 1024 bytes. Todo lo que exceda de aquí… depende del navegador. Firefox p.ej. acepta URLs muy largas, pero recordad que Base64 genera fácilmente URLs no muy largas, sinó descomunalmente largas (si la imágen ocupa 100Ks, la URL en Base64 ocupará más de 100Ks). Así que para imágenes pequeñas igual tiene sentido, pero para imágenes largas, honestamente no creo que sea una buena solución.


  3. La mayoría de navegadores modernos soportan data urls (IE lo empezó a hacer con su versión 8).



En fin… bueno, como curiosidad no está mal, eh? ;-)



Gracias a Pablo Nuñez por la divertida e interesante conversación en twitter!



Saludos!



PD: Enlace al post anterior de esa serie: http://geeks.ms/blogs/etomas/archive/2011/03/15/asp-net-mvc-previsualizar-im-225-genes-subidas-1.aspx



PD2: Otro crosspost des de mi blog en geeks.ms

ASP.NET MVC: Previsualizar imágenes subidas (1)

Buenas! Una pregunta que últimamente parece que se pregunta varias veces en los foros de ASP.NET MVC es como previsualizar una imagen que se quiere subir al servidor.

Antes que nada aclarar que, técnicamente, la pregunta está mal hecha: no es posible previsualizar la imagen antes de que sea subida. Antiguamente en algunos navegadores, y con un poco de javascript, eso era posible, pero ahora por suerte eso ya no funciona :)

Básicamente previsualizar una imagen consiste en:

  1. Recibir los datos de la imagen
  2. Guardarla en algún sitio “temporal”
  3. Mandarla de vuelta al navegador
  4. Borrar la imagen del sitio “temporal” si el usuario no la acepta

En este primer post vamos a ver como hacerlo sin usar Ajax. Luego habrá un segundo post y veremos como hacerlo usando Ajax (no es tan sencillo porque XMLHttpRequest no soporta el formato de datos multipart/form-data que es el que se usa cuando se mandan ficheros).

Bien, vamos a verlo rápidamente, ya veréis cuan sencillo es :)

Lo primero es tener la vista que tenga el formulario para enviar los datos. La siguiente servirá (si queréis más detalles de como hacer upload de ficheros en MVC mirad mi post al respecto):

<h2>Index</h2>

<form enctype="multipart/form-data" method="post" action="@Url.Action("SendImage")">
<label for="img">Seleccionar imagen:</label>
<input type="file" name="img" /> <br />
<input type="submit" />
</form>





Trivial: un form con multipart/form-data que enviará sus datos a la acción SendImage. La acción podría ser algo como:




[HttpPost]
public ActionResult SendImage(HttpPostedFileBase img)
{
var data = new byte[img.ContentLength];
img.InputStream.Read(data, 0, img.ContentLength);
var path = ControllerContext.HttpContext.Server.MapPath("/");
var filename = Path.Combine(path, Path.GetFileName(img.FileName));
System.IO.File.WriteAllBytes(Path.Combine(path, filename), data);
ViewBag.ImageUploaded = filename;
return View("Index");
}





Vale, fijaos que recibimos el HttpPostedFileBase (llamado img como el atributo name del input type=”file”). Lo que hacemos en la acción es muy simple:




  1. Guardamos la imagen en disco (en este caso por pura pereza uso el directorio raíz de la aplicación web. Por supuesto lo suyo es usar un directorio configurado en web.config y con los permisos NTFS correspondientes).


  2. Coloco en el ViewBag el nombre de la imagen que se ha guardado (luego vemos porque).


  3. Devuelvo la vista “Index” (la misma de la cual venimos)



Ok, tal y como lo tenemos ahora, si lo probáis veréis que podéis hacer un upload de la imagen, y la imagen se graba en el disco (en el directorio raíz de la aplicación web), pero no se previsualiza nada. Vamos a modificar la vista Index para que haya esa previsualización.



Para previsualizar la imagen basta con añadir el siguiente código, despues del </form> en la vista:




@if (!string.IsNullOrEmpty(ViewBag.ImageUploaded))
{
<img src="@Url.Action("Preview", new {file=ViewBag.ImageUploaded})" />
}





Simple, no? Si en el ViewBag existe la entrada ImageUploaded generamos un tag <img> cuya dirección es la acción “Preview” y le pasamos el parámetro file con el nombre de la imagen. Y como es la acción Preview? Pues super sencilla:




public ActionResult Preview(string file)
{
var path = ControllerContext.HttpContext.Server.MapPath("/");
if (System.IO.File.Exists(Path.Combine(path, file)))
{
return File(Path.Combine(path, file), "image/jpeg");
}
return new HttpNotFoundResult();
}





Simplemente leemos la imagen guardada y la devolvemos usando un FilePathResult. Simple, eh? ;-)



Con eso, si subís una imagen, cuando le deis a submit, aparecerá de nuevo la vista, pero ahora previsualizando la imagen.



Ahora sólo nos queda el punto final: Que el usuario pueda aceptar esa imagen como correcta. Para ello lo más simple es hacer lo siguiente:




  1. Si el usuario está previsualizando una imagen y NO ha seleccionado otra, cuando hace el submit se entiende que acepta dicha imagen.


  2. Si el usuario NO está previsualizando una imagen, o bien está previsualizando una pero selecciona otra, al hacer el submit se entiende que descarta la imagen anterior y quiere previsualizar la nueva.



Para ello, tenemos que hacer que la vista le indique al controlador si se está previsualizando una imagen, y cual és. Por suerte eso es muy sencillo. Basta con modificar el <form> de la vista para que su atributo action quede como:




<form enctype="multipart/form-data" method="post" 
action="@Url.Action("SendImage", new {previewed=ViewBag.ImageUploaded})">





Fijaos que añadimos un parámetro previewed que la vista mandará a la acción y que será el nombre de la imagen que se está previsualizando.



Vamos a modificar la acción para que reciba ese parámetro y actúe en consecuencia:




[HttpPost]
public ActionResult SendImage(HttpPostedFileBase img, string previewed)
{
if (!string.IsNullOrEmpty(previewed) && img == null)
{
ViewBag.ImageName = previewed;
return View("Step2");
}

// Código tal cual estaba
}





Añadimos ese if al inicio: Si el usuario está previsualizando una imagen y no ha seleccionado otra, entendemos que acepta esta imagen. Entonces guardamos el nombre en el ViewBag y lo mandamos a la siguiente vista (Step2).



La vista es muy sencilla:




<h2>Step2</h2>

Siguiente paso. El usuario ha aceptado la imagen: <br />

<img src="@Url.Action("Preview", new {file=ViewBag.ImageName})" />





Simplemente mostramos la imagen. Fijaos que de nuevo usamos la acción Preview, puesto que la imagen ya la tenemos guardada en el servidor.



Notas finales



Faltarían, al menos, dos cosas para que dicho proyecto funcionase “de forma aceptable”:




  1. Borrar las imagenes descartadas por el usuario (en la acción SendImage si se recibe una imagen nueva y se estaba previsualizando una, borrar esta ya que el usuario la ha descartado).


  2. La acción  Preview siempre devuelve content-type a image/jpeg, eso debería hacerse según la extensión de la imagen, o guardarlo en algún sitio.



Ambas cosas son triviales y no las añado porque lo único que consiguiría es liar el código un poco más ;-)



En el próximo post… lo mismo pero usando Ajax.



Saludos!



PD: Pues sí… otro crosspost de mi blog de geeks.ms! Que raro, no? :P