Category Archives: Javascript

Subir archivos a S3 directo con AJAX

Una gran característica de S3 es que puedes subir directamente a tu bucket a través de una acción post, el flujo es muy simple, solo necesitas generar algunos parámetros de la ubicación donde se almacenará y los permisos/meta-información, con el SDK de Amazon para PHP, puedes hacerlo fácilmente con:

require_once 'sdk.class.php';
	$s3_uploader = new S3BrowserUpload();
	$params = $s3_uploader->generate_upload_parameters($bucket, $expires, array(
		'acl' => AmazonS3::ACL_PUBLIC,
		'key' => $path_file,
		'Content-Type' => $mime,
	));

El resultado de $params contendrá un arreglo cuyo índice $params['input'] necesita ser pasado a valores ocultos de una forma y finalmente necesitas agregar un tipo file donde el usuario seleccionará el archivo (al final de los otros parámetros):

<input type="file" name="file" />

También puedes especificar la dirección URL donde regresará el navegador después que el usuario haya subido el archivo.

Esto trabaja muy bien para subir archivos de “forma tradicional”, pero ¿cómo lo harías si quisieras tomar ventaja de las nuevas API’s de HTML5 como almacenar una cadena en un archivo; o obtener la imagen de un canvas y almacenarlo directo en S3 sin enviarlo primero a un servidor; o hacer una miniatura en el cliente y subirla directo a S3?

El primer problema aquí es que no puedes hacer llamadas crossdomain (interdominio), debido a que S3 no tiene la característica de agregar una cabecera de Access-Control-Origin que permitiría enviar el archivo de forma directa, afortunadamente con postMessage puedes subir un archivo HTML y uno javascript a tu cuenta S3 y funcionará mediante un “proxy”.

El otro problema es que postMessage solo permite tipos que puedan ser clonados, por seguridad File/FileReader no pueden ser clonados por lo que necesitas pasar el contenido del archivo directamente en lugar del objeto.

Este ejemplo te permitirá

  1. Seleccionar una imagen
  2. Reducirla a la mitad de tamaño mediante un canvas
  3. Obtener los datos binarios codificados en base64
  4. Pasar mediante un postMessage el contenido
  5. Decodificar los datos en base64
  6. Pasarlo mediante AJAX a tu cuenta de S3

Esto es simple debido a que se están utilizando características estándar; pero desafortunadamente como HTML5 es un estándar en progreso, muchos navegadores aún no lo soportan, por el momento lo soportan Firefox 4+ and Chrome 11+

Primero revisaremos la parte que será almacenada en tu bucket de S3 y servirá como proxy, tendremos estos tres archivos:

  1. index.html
    Contendrá solo código para cargar el javascript y nada más
  2. base64-binary.js
    Es un archivo útil que permite decodificar la imagen en base64 a un ArrayBuffer que será convertido en un Blob y será enviado como un “archivo” en una acción POST
  3. upload.js
    Este archivo contendrá la lógica que recibirá el mensaje y convertirá la imagen codificada en base64 con parámetros POST y lo convertirá en una petición AJAX

Puedes ver todos los archivos aquí:
https://github.com/danguer/blog-examples/tree/master/html5/s3/direct-upload/s3-files

La parte principal de upload.js es:

window.addEventListener("message", function(event) {
	var data = event.data; //obtener los datos del mensaje

	//subir el archivo mediante blob
	var separator = 'base64,';
	var index = data.content.indexOf(separator);
	if (index != -1) {
		var bb = new BlobBuilder();

		//decodificar los datos de Base64 y convertirlos a un ArrayBuffer
		var barray = Base64Binary.decodeArrayBuffer(data.content.substring(index+separator.length));
	        bb.append(barray); 

	        var blob = bb.getBlob();

	        //pasar los parámetros POST mediante FormData
	        var formdata = new FormData();

		for (var param_key in data.params) {
			formdata.append(param_key, data.params[param_key]);
		}
		formdata.append("file", blob, "myblob.png"); //agregar como Blob

		//finalmente enviar los datos mediante AJAX
		var xhr = new XMLHttpRequest();
		xhr.open("POST", data.url, true);
		xhr.send(formdata);
	}
}, false);

Como puedes ver solamente estamos decodificando el base64 y convirtiéndolo a un Blob, pasando todos los parámetros mediante FormData y enviándolos mediante AJAX.

Como este archivo estará almacenado en S3 no habrá ningún problema de crossdomain, pero necesitas poder crear un Blob y un FormData para que funcione.

 

En el script para cargar el archivo tendremos un poco más de código, primero necesitamos código para generar los parámtros de S3, esto se puede hacer fácilmente en PHP con::

<?php
	//iniciar aws-sdk
	require_once 'sdk.class.php';

	//la respuesta será en JSON
	header('Content-Type: application/json');	

	$bucket = 'danguer-blog'; //tu bucket en s3
	$bucket_path = 'upload-html5/tmp'; //"directorio" donde se guardarán los archivos
	$filename = isset($_POST['name'])?$_POST['name']:null;
	$filemime = isset($_POST['mime'])?$_POST['mime']:null;

	//manejar errores
	if (empty($filename)) {
		print json_encode(array(
			'error' => 'debe enviar el nombre',
		));
		return;
	}

	if (empty($filemime)) {
		print json_encode(array(
			'error' => 'debe enviar el mime',
		));
		return;
	}

	if (strpos($filename, '..') !== false) {
		print json_encode(array(
			'error' => 'no se aceptan rutas relativas',
		));
		return;
	}

	$expires = '+15 minutes'; //el token solo será válido por 15 minutos
	$path_file = "{$bucket_path}/{$filename}";
	$mime = $filemime; //mandar el contenido mime para ayudar a los navegadores

	//obtener los parámetros de S3 para subir directamente
	$s3_uploader = new S3BrowserUpload();
	$params = $s3_uploader->generate_upload_parameters($bucket, $expires, array(
		'acl' => AmazonS3::ACL_PUBLIC,
		'key' => $path_file,
		'Content-Type' => $mime,
	));

	print json_encode(array(
		'error' => '',
		'url' => "http://{$params['form']['action']}",
		'params' => $params['inputs']
	));
	return;

 

Después necesitamos presentar la entrada del archivo y crear un iframe que cargue los archivos S3 que hemos subido:

	<!-- hiden frame -->
	<iframe id="postMessageFrame" src="<?=$url_iframe?>">
	</iframe>

	<h3>Subir archivos</h3>
	<input type="file" accept="image/*" onchange="uploadFile(this.files)">

Y el código javascript es un poco más, debido a que una vez que el usuario seleccione un archivo, necesitamos cambiarle el tamaño

function resizeImage(file, mime) {
	var canvas = document.createElement("canvas");
        var ctx = canvas.getContext("2d");
        var canvasCopy = document.createElement("canvas");
        var ctxCopy = canvasCopy.getContext("2d");

	var reader = new FileReader();
        reader.onload = function() {
	    var image = new Image();
	    image.onload = function() {
                //escalar la imagen a la mitad
		canvasCopy.width = image.width;
	        canvasCopy.height = image.height;
	        ctxCopy.drawImage(image, 0, 0);

	        canvas.width = image.width * 0.5;
	        canvas.height = image.height * 0.5;
	        ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);

	        //convertirlo en imagen y obtener el binario codificado en base64
	        //para enviarlo mediante postMessage
	        var url = canvas.toDataURL(mime, 0.80);
	        uploadImage(file, url)
	    }

        image.src = reader.result;
    };

	//leer contenidos
	reader.readAsDataURL(file);
}

Como puedes ver estamos abriendo el archivo seleccionado, lo dibujamos en un canvas, lo copiamos en otro canvas a la mitad de su tamaño y obtenemos su representación binaria en base64 con el mismo mimetype del original, finalmente llamaremos para subir la imagen:

function uploadImage(file, dataURL) {
	//cargar firma para S3
	var signature_params = {
		mime: file.type,
		name: file.name
	};

	$.post(url_ajax_signature, signature_params, function(response) {
		//enviar mediante postMessage
		var windowFrame = document.getElementById('postMessageFrame').contentWindow ;
		var data = {
			params: response.params,
			url: response.url,
			content: dataURL
		}

		//enviar los datos de la firma de S3 y los datos binarios en base64
		windowFrame.postMessage(data, 'http://<?=$url_iframe_host?>');
	}, 'json');
}

Primero obtenemos los parámetros POST para S3 mediante AJAX del archivo PHP y después enviamos esos datos mediante postMessage al iframe en S3 para que pueda procesarlo y cargarlo a S3 sin necesidad de primero enviar el archivo al servidor y de ahi cargarlo a S3, con esto podemos cargar directamente a S3 desde el navegador del cliente y si es necesario podemos verificar los contenidos para evitar que se suba archivos maliciosos.

Todo el código se puede ver aquí:

https://github.com/danguer/blog-examples/tree/master/html5/s3/direct-upload

Decodificando Base64 Binario en Javascript

Actualmente todo los decodificadores de base64 en javascript son para cadenas, no funcionan para datos binarios; el ejemplo es cuando usas un elemento canvas y obtienes su representación base64 ( canvas.toDataURL() ) usualmente enviarás la cadena al servidor y ahí harás la decodificación en base64, si quieres procesar los datos en javascript encontrarás que fallará debido a que lo procesará como cadena.

Este script está usando los nuevos “array typed” (supongo que traducido como Arreglos con Tipo) de javascript, el uso básico es para decodificar una cadena base64 en un Uint8Array lo puedes utilizar como:

var uintArray = Base64Binary.decode(base64_string);

La otra llamada es cambiar el Uint8Array en un ArrayBuffer, esto es muy útil por ejemplo para subir un archivo mediante FormData ( formdata.append("file", arraybuffer, "test.png") ), puedes usarlo como:

var byteArray = Base64Binary.decodeArrayBuffer(base64_string);

Puedes checar el código aqui:
https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js

Ejemplo de API de Yahoo/Weather.com

La pagina de Yahoo developers tiene el API de Weather.com (que compro hace tiempo), Weather.com ya tenia un xml que ha simplificado Yahoo.com

Esta API nos permite obtener informacion del clima de practicamente cualquier lugar del mundo y sin duda es muy utilizada para varios widgets dentro de páginas o como widgets de escritorio.

Básicamente para usar esta API se hace una llamada a la siguiente dirección:
http://weather.yahooapis.com/forecastrss?p=CODIGO&u=c

Donde código puede ser un Código Postal de Estados Unidos, o un ID de una ciudad en el mundo, el API de yahoo developers, dice brevemente que para conseguir este ID se necesita ir a la página de Yahoo Weather y copiar de la URL el código de la ciudad.

Esto es realmente muy malo ya que lo que se busca es optimizar, pero ahi entra el API viejo de Weather.com, donde podemos enviarle una cadena con la ciudad a buscar y nos regresará diversos resultados que pueden concordar con el código de la ciudad y su nombre “legible”, la url es: 
http://xoap.weather.com/weather/search/search?where=NOMBRE_CIUDAD

Por ejemplo para buscar la ciudad de Puebla, Mexico haremos:
http://xoap.weather.com/weather/search/search?where=Puebla

El resultado nos dará como resultado:

<!-- This document is intended only for use by authorized licensees of The  -->
<!-- Weather Channel. Unauthorized use is prohibited.  Copyright 1995-2008, -->
<!-- The Weather Channel Interactive, Inc.  All Rights Reserved.            -->
<search ver="2.0">
  <loc id="MXPA0070" type="1">Puebla, Mexico</loc>
</search>

 

Que vemos que el elemento loc provee el ID de la ciudad y el nombre legible; ahora si podemos contruir la nueva URL que será:

http://weather.yahooapis.com/forecastrss?p=MXPA0070&u=c

El resultado es un documento RSS con un nombre de espacio (namespace) de yweather, la página contiene mucha información, mucha de ella redundante, pero lo que más nos importa es lo que tiene ese nombre de espacios como:

<yweather:condition  text="Partly Cloudy"  code="30"  temp="19"  date="Sat, 27 Sep 2008 5:49 pm CDT" />
<yweather:location city="Puebla" region=""   country="MX"/>
<yweather:units temperature="C" distance="km" pressure="mb" speed="kph"/>
<yweather:wind chill="19"   direction="150"   speed="11.27" />
<yweather:atmosphere humidity="60"  visibility="1607.69"  pressure="0"  rising="0" />
<yweather:astronomy sunrise="7:23 am"   sunset="7:24 pm"/>

El primero es lo más importante (condition), nos mostrará la información del clima, el código de estado (code), la temperatura (temp) y la fecha de cuando se obtuvo la información.
El segundo (location) nos dice información del lugar.
El tercero (units) nos dice en qué unidades está expresada la información, como hemos solicitado en formato de grados Centigrados el sistema muestra la información en sistema métrico.
El cuarto (wind) nos da información del viento.
El quinto (atmosphere) nos da información del medio ambiente en cuanto a humedad, visibilidad, etc
El último (astronomy) nos da una breve información de cuando el sol aparece y se oculta en el horizonte.

La página también incluye una liga a la imagen del estado, yo he decidido utilizar las que provee Weather.com

El nombre del estado no lo usaremos ya que está en inglés, por lo que hice una traducción del estado al nombre correspondiente, los códigos se pueden ver aqui:
http://developer.yahoo.com/weather/#codes

 

Aqui tenemos un ejemplo ensamblado en PHP y Javascript:
http://demo.livesourcing.com/blog_vida/articulos/DanguerArticle_Clima/

 

Básicamente tiene tres partes, la primera nos permite introducir un nombre y obtener el ID de los lugares, obviamente está en ingles por lo que se tienen que hacer busquedas como “Mexico City” para encontrar la información del clima del D.F.

La segunda parte nos permite seleccionar de las distintas opciones de los lugares el que queramos ver su clima, después de seleccionarlo le damos click a “Obtener clima” y obtendremos el clima ;)

El sistema muestra solamente el Estado (en español), la temperatura y la fecha con la imagen correspondiente del estado; en el ejemplo no obtuve información extra como información del viento o del medio ambiente, pero en base al ejemplo se puede extender fácilmente.

 

Descargar el código fuente del ejemplo: 
http://demo.livesourcing.com/blog_vida/articulos/DanguerArticle_Clima.tar.gz

 

Más información:

 

Frameworks en Javascript

Sin duda los frameworks son una gran idea porque nos ayudan a desarrollar más rápido y sobre todo con una interfaz mas amena al usuario, aqui hay algunos de los frameworks que utilizo o he utilizado:

Prototype y script.aculo.us

Uno de los frameworks más viejos y que inicio con el movimiento AJAX y el de la Web 2.0
De hecho prototype parece ser el que “patentó” las abreviaturas como $(…) para acortar el document.getElementById, algo que otros frameworks también tendrán de forma similar

Ventajas: Limpio y fácil de usar, se usan de manera separada
Desventajas: No tiene muchos widgets

Página de Prototype Página de Script.aculo.us

jQuery

jQuery es una de esas librerías que en el momento en que la comienzas a utilizar no la dejarás ;) es muy simple de utilizar y lo que la llevo a la cima fueron los selectores, esta es una manera muy útil de seleccionar en primer lugar elementos DOM que cumplan con una regla, por ejemplo con jQuery $(‘img.miClase’) estamos seleccionando todas las imagenes que tengan como clase la de nombre “miClase”, esto es algo que toma muchas lineas en javascript “normal”, también regresa esto como un arreglo por lo que se puede ir alterando uno a uno o utilizando sus funciones de jQuery

El manejo de AJAX es un muy simple (como en la mayoría de frameworks), y por último se está trabajando en widgets para darle más poder a este framework

Ventajas: Pequeño, muy poderoso, fácil de usar, documentación bien estructurada (en la mayoría de casos)
Desventajas: No hay widgets, la parte jQuery-UI que se encarga de esto aún está en desarrollo y puede resultar en más tipo de prototipo rápido que en aplicaciones robustas

Página de jQuery Página de JQuery-UI

Mootools

Mootools es otra de esas herramientas que generan adicción, sus grandes ventajas están del lado gráfico ya que tiene efectos muy cuidados y algunos widgets que son muy útiles. También extiende algunas clases de uso común como Array o String para añadirle funciones útiles.

Las nuevas versiones también incluyen selectores aunque más básicos que jQuery

Ventajas: Efectos fáciles de utilizar, extiende funciones directamente a clases nativas
Desventajas: Falta de widgets avanzados

Página de mootools

YUI

Una Mega-Librería de Javascript, básicamente es un conjunto de widgets muy bien ensamblados y programados. Si se necesita programar algo a nivel empresarial sin duda YUI es la mejor solución, incluye widgets como autocompletado o el de calendario (que lo catapultaron a la fama), así como manejo de AJAX en una forma un poco más complicada (si se puede decir de esta manera) pero también más uniforme.

Por ejemplo es fácil crear un dialogo que envie la información de un formulario mediante AJAX debido a que fue pensado en muchos patrones de diseño y  programación.

Ventajas: Muchos widgets, uniformidad entre componentes, muy buena documentación y una gran cantidad de ejemplos.
Desventajas: El selector es una característica nueva (BETA), archivos gigantescos (en términos de javascript y que alcanzan un promedio de 500KB para descargar algunos componentes, en espacio de disco puede llegar hasta más de 10 MB si se descomprimen todas las versiones), lamentablemente como muchos componentes están relacionados no hay manera de “cortarlos” (aunque es posible no incluir los elementos adicionales para aliviar un poco la descarga de archivos, pero aún asi la arquitectura debio ser distinta y no incluir todo en un mega-componente)

Página de YUI

Ext-JS

Es una gran librería con muchos widgets de uso para una aplicación empresarial Web 2.0, tiene demos muy interesantes y provee soporte de licencia comercial u open source.

Ventajas: Muchos widgets
Desventajas: No lo he probado ;)

Página de ExtJS

Otras frameworks que prometen saltar a la fama si son llevados a cabo de manera cuidadosa:

Dojo

Aunque siento que Dojo no ha encontrado su “nicho” (a pesar de que está soportado por Zend Framework en la parte de Ajax), este puede ser la parte gráfica desde javascript, por supuesto javascript no puede dibujar sobre el navegador pero si lo puede hacer mediante otros plugins como el caso de SVG, Silverlight o Flash, al parecer ahora solo soporta SVG y Silverlight y no han implementado Flash que es mejor para esto.

Spry

Esta tecnología hay que observarla de cerca porque está madurando y va a ser un modelo a seguir en los próximos años.

Básicamente es un conjunto de widgets que se incrustan mediante xhtml en una página, así en vez del “aburrido” javascript de una vez que se ha cargado el documento asignar variables mediante id’s, nombres, etc; podemos hacer algo como:

<div id="productMenu" spry:region="dsProducts" style="display: none;">
	<table>
		<tr spry:repeat="dsProducts" spry:hover="hover" spry:suggestion="{name}">
			<td><div class="boxshot"><img src="../../demos/products/{boximage}" alt="{name}" /></div><div>{name}</div></td>
		</tr>
	</table>
</div>

Donde claramente se notan partes que serán rendereados en javascript, algo muy innovador.

Página de Spry

Usando el Google Autosuggest API

Aunque no he encontrado documentación, viendo como Youtube solicita datos de sugerencia me he dado cuenta que existe un pequeño api de sugerencias google, la liga es:

http://suggestqueries.google.com/complete/search?q=lorem

Si entran a la anterior dirección verán que les sirve un documento javascript con una llamada a una función (window.google.ac.h)

El documento regresa un arreglo con tres valores, el primero es la busqueda misma, el segundo es un arreglo de etiquetas y el tercero es un arreglo con la información del número de resultados. Este formato puede cambiar dependiendo de los parámetros enviados.

Hasta donde he probado tiene las siguientes opciones (pronto liberarán las especificaciones oficiales):

  • nolabels, valores posibles: t, f
    Especifica si deben mostrarse el numero de resultados [f] o solamente las etiquetas [t]
  • hl, valores posibles: cualquier valor válido ISO para lenguajes por ejemplo en, es, etc
    El lenguaje en el que se realizará la búsqueda por defecto está en ingles, se puede cambiar a español con hl=es
  • json, valores posibles: t
    Especifica si el resultado será en formato JSON (para ser leido por javascript), cambia un poco el formato ya que no llama la función y no es un arreglo doble como por defecto
  • xml, valores posibles: t
    Especifica si el resultado será en formato XML (muy bueno para PHP, flash o Flex), tiene una información un poco más “limpia” pero por supuesto contiene es más grande el envio de este archivo
  • jsonp, valor posible: nombre de función en javascript
    Con este parámetro podemos decirle que mande a llamar a una función de nuestro código por ejemplo: “resultados”

Pueden ver un ejemplo donde proceso los resultados de google suggest en la siguiente página:

Ejemplo de Google Autosuggest API

http://demo.livesourcing.com/blog_vida/articulos/DanguerArticle_GoogleAutosuggest/

Pueden descargar el código en:
http://demo.livesourcing.com/blog_vida/articulos/DanguerArticle_GoogleAutosuggest.tar.gz