jueves, diciembre 01, 2011
mozilla rhino 1.7R3 javadocs apidocs mirror
http://marianoguerra.com.ar/docs/rhino-1-7R3/index.html
PD: SEO friendly title :P
martes, noviembre 02, 2010
CouchApp V: filtrar documentos por tag ordenados por fecha
ahora, como filtro documentos por tag?
si puedo hacer eso, como filtro por tag y ordeno por fecha?
ya que estamos, no seria lindo poder filtrar por tag *y* por fecha?
vamos a resolver todos estos requerimientos con una simple vista
primero creamos la vista
couchapp generate view by-tag
esto crea una vista en el directorio views llamado by-tag, este es un directorio que contiene dos archivos, map.js y reduce.js
en este caso vamos a usar solo map.js asi que borra reduce.js
cd views/by-tag rm reduce.js
ahora edita map.js para que se vea como esto
function(doc) {
if (doc.tags) {
doc.tags.forEach(function (element, index) {
emit([element, doc.created], doc);
});
}
}
por cada documento en la base de datos, vemos si tiene el atributo tags, si lo tiene, por cada tag en tags emitimos un documento cuya llave es un array con el tag y la fecha y cuyo valor es el documento en si.
ahora empujamos los cambios a couchdb
couchapp push
para probar que funciona, crea algunos documentos con el un campo llamado tags que contenga un array de strings y otro campo llamado created que contenga el timestamp en el que el documento fue creado
si pongo esta URL http://localhost:5984/datos/_design/datos/_view/by-tag/ en mi navegador, obtengo algo así:
{"total_rows":8,"offset":0,"rows":[
{"id":"d6de7e9a63039dc1af500a40af0014d7","key":["bar",1288644825761],"value":{"_id":"d6de7e9a63039dc1af500a40af0014d7","_rev":"1-eab86fbc2b4c24f31e1d60dfdd762793","author":"wariano", "created":1288644825761, "tags":["test","foo","bar"], ...}},
...
]}
esto significa que la vista funciono, ahora para filtrar por tag la URL se va a poner un poco rara
vamos a usar filtros en la vista para filtrar solo los documentos con un tag especifico
http://localhost:5984/datos/_design/datos/_view/by-tag?descending=false&startkey=["test", 0]&endkey=["test", 9999999999999]
con este request decimos que queremos los resultados de la vista llamada by-tag, filtrando los documentos empezando con la llave ["test", 0] y terminando con la llave ["test", 9999999999999]. Esto significa que solo queremos los documentos con la llave "test" y que queremos todos los timestamps (por eso el numero enorme en endkey)
si queremos ordenar los tags en orden descendente deberíamos cambiar el orden de starkey y endkey: http://localhost:5984/datos/_design/datos/_view/by-tag?descending=true&startkey=["test", 9999999999999]&endkey=["test", 0]
podemos jugar con startkey y endkey para obtener rangos de tags o un tag en un periodo de tiempo especifico, por ejemplo: "cosas taggeadas con fun en los últimos dos días"
el código para hacer el request a couchdb desde javascript es el siguiente
datos.getByTag = function (tag, descending, okCb, errorCb, startStamp, endStamp) {
var tmp;
startStamp = startStamp || 0;
endStamp = endStamp || 9999999999999;
if (descending) {
tmp = endStamp;
endStamp = startStamp;
startStamp = tmp;
}
$.couch.db(datos.db).view("datos/by-tag",
{"descending": descending, "startkey": [tag, startStamp], "endkey": [tag, endStamp],
"success": okCb, "error": errorCb});
};
con esto tenes una forma de listar documentos por uno o mas campos, podes modificar este ejemplo un poco para listar por usuario o por otras cosas
[EN] CouchApp V: filter documents by tag ordered by timestamp
you now have documents and to be in this century you decide to add tags to some elements so you can filter and categorize (say folksonomy and you will sound really hip!)
now, how do I filter the documents by tag?
if I can do that, how do I filter by tag and order by date?
now that we are at it, wouldn't be nice to have a filter by tag *and* date?
we will solve all this requirements with a simple view
first we create the view
couchapp generate view by-tag
this creates a view in the views called by-tags, this is a directory that contains two files, map.js and reduce.js
in this case we will only use the map.js file, so remove the reduce.js file
cd views/by-tag rm reduce.js
now edit the map.js file to look like this
function(doc) {
if (doc.tags) {
doc.tags.forEach(function (element, index) {
emit([element, doc.created], doc);
});
}
}
here for each document in the database we check if the document has the tags attribute and if it has the attribute, for each tag we emit a document that contains as key an array with the tag and timestamp when the document was created and as value the document itself
now we push our changes to couchdb
couchapp push
to test that this works, create some documents with a field called tags that contains a list of strings and a field called created that contain the timestamp when the item was created
if I put this URL http://localhost:5984/datos/_design/datos/_view/by-tag/ in my browser I get something like this
{"total_rows":8,"offset":0,"rows":[
{"id":"d6de7e9a63039dc1af500a40af0014d7","key":["bar",1288644825761],"value":{"_id":"d6de7e9a63039dc1af500a40af0014d7","_rev":"1-eab86fbc2b4c24f31e1d60dfdd762793","author":"wariano", "created":1288644825761, "tags":["test","foo","bar"], ...}},
...
]}
this means the view worked, now to filter by tag the URL will get weird
we will use filters in the view to filter only for a specific tag
http://localhost:5984/datos/_design/datos/_view/by-tag?descending=false&startkey=["test", 0]&endkey=["test", 9999999999999]
with this request we say that we want to get the result of the view called by-tag, filtering starting with the key ["test", 0] and ending with the key ["test", 9999999999999]. This means that we only want the documents with the key "test" and we want all the timestamps (that's why the huge number in the endkey
if we want to sort the tags in descending order we should switch the start and endkey like this: http://localhost:5984/datos/_design/datos/_view/by-tag?descending=true&startkey=["test", 9999999999999]&endkey=["test", 0]
we can play with startkey and endkey to get a range of tags or one tag in a specific period, for example, "things tagged fun in the last 2 days"
the code to do the request to couchdb from javascript is the following
datos.getByTag = function (tag, descending, okCb, errorCb, startStamp, endStamp) {
var tmp;
startStamp = startStamp || 0;
endStamp = endStamp || 9999999999999;
if (descending) {
tmp = endStamp;
endStamp = startStamp;
startStamp = tmp;
}
$.couch.db(datos.db).view("datos/by-tag",
{"descending": descending, "startkey": [tag, startStamp], "endkey": [tag, endStamp],
"success": okCb, "error": errorCb});
};
with this you have a way to list documents by one or more fields, you can modify this a little to list by users, or by some other thing
viernes, octubre 22, 2010
CouchApp I: instalando el entorno
vamos a empezar nuestra couchapp instalando el entorno, para eso necesitamos instalar couchapp y couchdb
# instalar couchdb y los modulos de python para poder instalar otros modulos sudo apt-get install python-setuptools couchdb # descargar pip curl -O http://python-distribute.org/distribute_setup.py sudo python distribute_setup.py sudo easy_install pip # instalar couchapp sudo pip install couchapp
ahora que tenemos couchapp vamos a generar nuestro proyecto, vamos a llamarlo "datos"
couchapp generate datos cd datos # editamos el archivo couchapprc vim .couchapprc
el archivo .couchapprc contiene los destinos donde vamos a instalar nuestra aplicación, definamos el target por defecto
{"env": {
"default": {
"db": "http://localhost:5984/datos"
}
}
}
guardar el contenido y correr
couchapp push
deberia mostrar algo como lo siguiente:
2010-10-22 10:21:10 [INFO] Visit your CouchApp here: http://localhost:5984/datos/_design/datos/index.html
ahora anda a esa URL con tu browser, deberías ver la pagina de demostración.
[EN] CouchApp I: setting up the environment
we will start our couchapp setting up the environment, for that we will install couchdb and couchapp
# install couchdb and python modules to install other modules sudo apt-get install python-setuptools couchdb # download pip curl -O http://python-distribute.org/distribute_setup.py sudo python distribute_setup.py sudo easy_install pip # install couchapp sudo pip install couchapp
now that we have couchapp we will generate our project, we will call it "datos"
couchapp generate datos cd datos # edit the couchapprc file vim .couchapprc
the .couchapprc file contains the targets where we can deploy our application, let's define the default target
{"env": {
"default": {
"db": "http://localhost:5984/datos"
}
}
}
save the content and run
couchapp push
it should display something like the following:
2010-10-22 10:21:10 [INFO] Visit your CouchApp here: http://localhost:5984/datos/_design/datos/index.html
now go to that site with your browser, you should see a demo page.
lunes, mayo 31, 2010
llega el momento en la vida de toda persona
Una idea que se me ocurrió hace un tiempo y a la cual fui un poco obligado ante la necesidad de mostrar bastante código y pelear por horas con oo impress, mas que mis slides son siempre similares y tengo que aplicar el mismo estilo a todo.
si, se que hay formas, pero las formas son mas complicadas de lo que desearía, ahora puedo tener mi código resaltado con pygments en archivos aparte, cargarlos a voluntad desde cualquier parte de la interwebs, cambiar el color del resaltado de sintaxis cambiando una archivo css, reusar estilos y slides a diestra y siniestra etc.
si lo quieren ver esta acá:
http://github.com/marianoguerra/pepete
unos slides introductorios (no podría ser de otra forma) acá, entra y apreta load:
http://marianoguerra.github.com/pepete/
disclaimer: los slides los hice a las patadas (no me gusta hacer slides :D)
viernes, abril 10, 2009
Presentacion javascript
martes, marzo 24, 2009
Como consumuir Webservice REST JSON en Javascript
ahora vamos a hacer un modulo en javascript para usar la API REST JSON desde una pagina web (lo que normalmente se llama Web 2.0 o AJAX solo que la X de XML la cambiamos por la J de JSON asi que queda AJAJ).
el primer problema que debemos superar es la regla de "same origin" de javascript por la cual solo podemos hacer requests asincronicos con HttpRequest solo a la misma URL de la cual se descargo el script, y como nuestra aplicación se va a distribuir en un jar, nuestro js y html tiene que estar en ese jar y lo tiene que servir la misma aplicación, lo que significa que si le pido 0.0.0.0:9999/pagina.html la aplicación tiene que buscar en el jar el archivo en algun lugar y devolverlo. Si el archivo viene de otro lado nuestro request va a fallar.
Para solucionar esto la unica forma que encontre fue escribir un handler a mano para devolver archivos estaticos.
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package addressbook;
import java.io.IOException;
import java.net.MalformedURLException;
import org.mortbay.jetty.handler.ResourceHandler;
import org.mortbay.resource.Resource;
/**
*
* @author mariano
*/
public class ContentHandler extends ResourceHandler{
@Override
public Resource getResource(String path) throws MalformedURLException
{
System.out.println(path);
try
{
System.out.println(getResourceBase().replaceAll("file:", "") + path);
return Resource.newResource(this.getClass().getResource(getResourceBase().replaceAll("file:", "") + path));
}
catch(IOException ex)
{
return null;
}
}
}
el archivo Main.java se modifica para agregar este handler a la lista de handlers, quedaria asi:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package addressbook;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.handler.DefaultHandler;
import org.mortbay.jetty.handler.HandlerList;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;
/**
*
* @author mariano
*/
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws Exception{
org.mortbay.jetty.Server server;
ServletHolder holder;
Context contextWS;
holder = new ServletHolder(ServletContainer.class);
holder.setInitParameter("com.sun.jersey.config.property.packages",
"addressbook.ws");
server = new org.mortbay.jetty.Server(9999);
contextWS = new Context(server, "/ws", Context.SESSIONS);
contextWS.addServlet(holder, "/*");
ContentHandler resource_handler = new ContentHandler();
resource_handler.setResourceBase("/resources");
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] { resource_handler, contextWS, new DefaultHandler()});
server.setHandler(handlers);
addressbook.ws.AddressBook.fill();
server.start();
}
}
ahora creamos un paquete llamado resources en la base del proyecto en donde vamos a poner nuestros archivos estaticos.
para poder hacer nuestra pagina über AJAX vamos a necesitar dos librerias, jquery y JSON.js, la primera nos ayuda en muchas cosas, siendo una de ellas la de hacer request HTTP que funcione en todos los browsers, la segunda nos sirve para serializar/deserializar objetos javascript.
las urls son las siguientes:
http://jquery.com/
http://www.json.org/json2.js
ponemos la libreria jquery-version.js y json2.js que descargamos en el paquete resources.js, creamos un archivo addressbook.js en el mismo paquete.
tambien creamos un archivo addressbook.html en el paquete resources, osea que la estructura quedaria asi:
/resources
/resources/addressbook.html
/resources/js
/resources/js/addressbook.js
/resources/js/jquery-1.3.1.min.js
/resources/js/json2.js
en addressbook.js vamos a crear los metodos de javascript que nos permitan interactuar con el servidor, asi como las clases que debemos enviar serializadas.
ahora pongo el contenido de los archivos.
resources/addressbook.html
<html>
<head>
<title>Address Book</title>
<link rel="stylesheet" type="text/css" href="css/style.css" />
<script src="js/jquery-1.3.1.min.js" type="text/javascript"></script>
<script src="js/json2.js" type="text/javascript"></script>
<script src="js/addressbook.js" type="text/javascript"></script>
<script type="text/javascript">
function isArray(obj)
{
if(typeof obj == 'object')
{
var criterion = obj.constructor.toString().match(/array/i);
return (criterion != null);
}
return false;
}
function search()
{
var query = $('#search').val();
AddressBook.search("all", query, onSearchSucceed, onSearchFailed);
}
function onSearchSucceed(response)
{
showResults(response);
}
function showResults(response)
{
var results = $('.results');
results.html("");
entries = JSON.parse(response);
if(entries == null)
{
return;
}
if(!isArray(entries["entry"]))
{
var item = entries["entry"];
results.append(item.firstName + " " + item.lastName + '<br/>');
return;
}
for(var i in entries["entry"])
{
var item = entries["entry"][i];
results.append(item.firstName + " " + item.lastName + '<br/>');
}
}
function onSearchFailed(response)
{
alert("La busqueda fallo");
}
function getAll()
{
AddressBook.getAll(onGetAllSucceeded, onGetAllFailed);
}
function onGetAllSucceeded(response)
{
showResults(response);
}
function onGetAllFailed(response)
{
alert("La busqueda fallo");
}
function create()
{
var firstName = $('#firstName').val();
var lastName = $('#lastName').val();
var address = $('#address').val();
var workPhone = $('#workPhone').val();
var homePhone = $('#homePhone').val();
var cellPhone = $('#cellPhone').val();
var entry = new AddressBook.AddressBookEntry(firstName, lastName, address, homePhone, workPhone, cellPhone);
AddressBook.create(entry, onCreateSucceeded, onCreateFailed);
}
function onCreateSucceeded(response)
{
alert("entrada añadida con exito");
}
function onCreateFailed(response)
{
alert("error añadiendo entrada");
}
</script>
</head>
<body>
<h1>Address Book</h1>
<table>
<tr><td>Nombre</td><td><input type="text" id="firstName"></td></tr>
<tr><td>Apellido</td><td><input type="text" id="lastName"></td></tr>
<tr><td>direccion</td><td><input type="text" id="address"></td></tr>
<tr><td>Telefono Trabajo</td><td><input type="text" id="workPhone"></td></tr>
<tr><td>Telefono Casa</td><td><input type="text" id="homePhone"></td></tr>
<tr><td>Telefono Celular</td><td><input type="text" id="cellPhone"></td></tr>
<tr><td></td><td><input type="button" value="Crear" onclick="create();"></td></tr>
</table>
<input type="text" id="search" />
<input type="button" value="Search" onclick="search();" />
<input type="button" value="Get All" onclick="getAll();" />
<div class="results">
</div>
</body>
</html>
resources/js/addressbook.js
// AddressBook namespace
var AddressBook = new Object();
AddressBook.AddressBookEntry = function(firstName, lastName, address, homePhone, workPhone, cellPhone) {
this.firstName = firstName;
this.lastName = lastName;
this.address = address;
this.homePhone = homePhone;
this.workPhone = workPhone;
this.cellPhone = cellPhone;
}
// serializa a JSON
AddressBook.AddressBookEntry.prototype.serialize = function()
{
return JSON.stringify(this);
}
AddressBook.AddressBook = function(entries)
{
this.entries = entries || [];
}
AddressBook.AddressBook.prototype.serialize = function()
{
return JSON.stringify(this);
}
// devuelve una funcion que hace un request REST a path, usando method, el
// metodo devuelto recibe como primer parametro una entidad que es enviada
// en el *cuerpo* del request, por ello este metodo es util para PUT y POST
AddressBook.buildCreateUpdate = function(method, path)
{
return function(entity, success_cb, error_cb)
{
$.ajax({url: path, type: method, dataType: "text", contentType: "application/json",
data: entity.serialize(),
success: success_cb, error: error_cb});
}
}
// devuelve una funcion que hace un request REST a path, usando method, el
// metodo devuelto recibe como primer un string que es agregado al path
// por ello este metodo es util para GET y DELETE
AddressBook.buildGetDelete = function(method, path)
{
return function(id, success_cb, error_cb)
{
$.ajax({url: path + id, type: method, dataType: "text",
success: success_cb, error: error_cb});
}
}
AddressBook.getAll = function(success_cb, error_cb)
{
var request = AddressBook.buildGetDelete("GET", "/ws/book/json/all");
request("", success_cb, error_cb);
}
AddressBook.search = function(type, query, success_cb, error_cb)
{
var request = AddressBook.buildGetDelete("GET", "/ws/book/json/search");
request("/" + type + "/" + query, success_cb, error_cb);
}
AddressBook.create = AddressBook.buildCreateUpdate("PUT", "/ws/book/");
tambien hice algunos cambios en addressbook.ws.AddressBook, aca esta la version actual
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package addressbook.ws;
import addressbook.model.AddressBookEntry;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
/**
*
* @author mariano
*/
@Path("book")
public class AddressBook {
private static addressbook.model.AddressBook addressBook;
public static addressbook.model.AddressBook search(String type, String query)
{
if(type.equals("firstName"))
{
return new addressbook.model.AddressBook(addressBook.searchByFirstName(query));
}
else if(type.equals("lastName"))
{
return new addressbook.model.AddressBook(addressBook.searchByLastName(query));
}
else
{
return new addressbook.model.AddressBook(addressBook.search(query));
}
}
public static addressbook.model.AddressBook getAddressBook()
{
if(addressBook == null)
addressBook = new addressbook.model.AddressBook();
return addressBook;
}
public static void fill()
{
getAddressBook();
addressBook.addEntry(new AddressBookEntry("Bob", "Esponja", "Fondo de Bikini 1", "123", "321", "5551"));
addressBook.addEntry(new AddressBookEntry("Gary", "Caracol", "Fondo de Bikini 2", "234", "432", "5552"));
addressBook.addEntry(new AddressBookEntry("Arenita", "Ardilla", "Fondo de Bikini 3", "345", "543", "5553"));
addressBook.addEntry(new AddressBookEntry("Patricio", "Estrella", "Fondo de Bikini 4", "456", "654", "5554"));
addressBook.addEntry(new AddressBookEntry("Calamardo", "Calamar", "Fondo de Bikini 5", "567", "765", "5555"));
}
@GET
@Path("xml/all")
@Produces("application/xml")
public static addressbook.model.AddressBook getAllXml()
{
return addressBook;
}
@GET
@Path("json/all")
@Produces("application/json")
public static addressbook.model.AddressBook getAllJson()
{
return addressBook;
}
@GET
@Path("xml/search/{type}/{query}")
@Produces("application/xml")
public static addressbook.model.AddressBook searchXml(@PathParam("type") String type, @PathParam("query") String query)
{
return search(type, query);
}
@GET
@Path("json/search/{type}/{query}")
@Produces("application/json")
public static addressbook.model.AddressBook searchJson(@PathParam("type") String type, @PathParam("query") String query)
{
return search(type, query);
}
@PUT
@Consumes({"application/json", "application/xml"})
public static void add(AddressBookEntry entry)
{
addressBook.addEntry(entry);
}
}
con este ejemplo tenemos una aplicación que expone una API REST y un cliente en javascript/html que lo consume y utiliza, expandiendo la cantidad de entidades en la API podemos escalar a una aplicacion completa.
Si ando con ganas en el proximo post voy a explicar como usar un ORM (object relational mapper) para almacenar el contenido de la entidad en una base de datos.
el codigo completo de este ejemplo lo voy a colgar en github en esta direccion http://github.com/marianoguerra/examples/tree/master, ahora a aprender a usar git :)
Seguidores
Archivo del Blog
-
►
2011
(74)
- ► septiembre (4)
-
►
2010
(111)
- ► septiembre (8)
-
►
2009
(96)
- ► septiembre (3)
-
►
2008
(60)
- ► septiembre (8)
-
►
2007
(64)
- ► septiembre (1)
-
►
2006
(81)
- ► septiembre (1)