Most Advanced Yet Acceptable

Tuesday, 18. May 2010 9:06

Raymond Loewy believed that

The adult public’s taste is not necessarily ready to accept the logical solutions to their requirements if the solution implies too vast a departure from what they have been conditioned into accepting as the norm.

Quite a statement. He synthesised  his observation in a principle and further compressed it in acronym: Most Advanced Yet Acceptable – MAYA

He was a radical, forward thinker so I guess he was constantly refitting his ideas into the Acceptable yet Advanced. As a industrial designer, he was trying to have his designs mass produced and sold to actual people. He succeeded and his designs reveal the tension between the future rolling in and the people holding back.

Of course,  it’s common sense. Had he designed something Unacceptable, his biography would have been different.

But MAYA is also a very real constraint, the sort that makes or kills your proposition. No matter what you are selling to whom: a new song, a new recipe, a new software; to your fans, to your girlfriend, to your boss. Being advanced is not enough, it must be acceptable. Not enough advanced, it is not interesting.

You can ignore the principle and be a scientist. Or never challenge it and be an engineer or just be boring. Some would say that a sciengineer will balance his act and live up to the MAYA principle. As a software developer and technology entusiast I find it hard to hit the sweet spot.

Category:Uncategorized | Comment (0)

ten principles of good design and two books

Saturday, 8. May 2010 23:02

I’m watching the first episode of The Genius of Design, a BBC documentary series exploring the history of design. It’s not about software design, but one could argue that the process and the craft of design are interesting not matter what is being designed. Anyway. They briefly interview Dieter Rams, a very prolific designer who made the history of industrial design and came up with ten commandments. Read them slowly. The 10th is pure genius.

  1. Good design is innovative
  2. Good design makes a product useful
  3. Good design is aesthetic
  4. Good design makes a product understandable
  5. Good design is unobtrusive
  6. Good design is honest
  7. Good design is long-lasting
  8. Good design is thorough down to the last detail
  9. Good design is environmentally friendly
  10. Good design is as little design as possible

(We are grateful to Mr Rams for such pearls of wisdom, and so is apple…)

All this talk about design reminds me of  the design of everyday things. It’s a brilliant book, inspiring and foundational. I keep suggesting it, especially to programmers and makers alike. the design of everyday things - coverAnother title worth mentioning is the design of design, from the same folk who wrote ”the mythical man-month”. The new book is not as ground-breaking as the old one, but it’s a good read of its own right.



On the subject of exploring and finding a design, watch this video on ted: build a tower, build a team (6 minutes).


Is there something like imdb or wikipedia for books? I keep linking amazon but I’d prefer to link to a website with a more informational angle.


Category:Uncategorized | Comment (0)

openid authentication handler for couchdb

Wednesday, 13. January 2010 17:09

Over at couchdb-openid, there is my implementation of OpenID version 1.1 for couchdb, based on http://github.com/etnt/eopenid

It seems fairly stable but has only been tested against myopenid.com, so it is definitely not production ready.

I plan to add support for openid 2.0 and to make couchdb work as openid endpoint.


The handler code would love to be reviewed by someone with some erlang and couchdb experience.

Demo

Try the login page at fortytwo.

Quick install:

  • cd couchdb_install_path/lib/couchdb/erlang/lib/
  • git clone git://github.com/mcaprari/couchdb-openid.git
  • cd couchdb-openid
  • make
  • edit local.ini [httpd]/authentication_handlers (or do it form futon) and
    add {couch_httpd_openid_auth, openid_authentication_handler} BEFORE the default handlers
  • restart couchdb

Quick test:

http://caprazzi.net:5984/_session?openid=auth-request&openid-identifier=<your_openid>

What to expect:

Only openid 1.1 is supported and it has only been tested with myopenid.com as openid provider.

When a client hits the initiation url (above), it is redirected to the openid provider
and prompted to authorise the association.

Then it’s redirected back to the couch and

  • if the client is not logged in in and supplies a new openid, a new user is created with username=openid and the client is logged in
  • if the client is not logged in in and supplies a mapped openid,
    the client is logged in as the mapped user
  • if the client is logged in and supplies a new openid,
    the supplied openid is added to current user, and the client keeps the current login
  • if the client is logged in and supplies a mapped openid
    • if openid is mapped to the same user, the client keeps the current login
    • if openid is mapped to a different user, the operation fails 400
  • if user is logged in AS ADMIN and supplies a new openid the operation fails 500

TODO:

  • try erl_openid for openid 2.0 support
  • decide if it is wise to map openids to admins (if at all possible)
  • cleanup ets table after auth confirm (or maybe find an alternative to ets tables)
  • reduce dependence from eopenid (dict access routines at least)

Category:Uncategorized | Comments (1)

Couchdb runtime statistics viewer

Tuesday, 5. January 2010 14:16

CouchDB comes with a runtime statistics module that lets you inspect how CouchDB performs.
The statistics module collects metrics like requests per second, request sizes and a multitude of other useful stuff. From Couchdb wiki.

I created a simple app that uses chronoscope to display how some metrics change over time.

Jump to the demo, go to the source or use the docs.

Couchdb Stats Screenshot

Install

couchdb-stats is a simple couchapp plus a python script that hits /_stats and stores the results in couchdb server

Attention: this app is tested with Couchdb 0.11.x, not yet released at the time of this post.

$ git clone git://github.com/mcaprari/couchdb-stats.git
$ cd couchdb-stats
$ couchapp push app http://localhost:5984/stats
$ python stats_copy.py localhost localhost stats 60

Couchdb reports metrics since server start or of the last 1, 5 or 15 minutes. There is no way to see yesterday’s stats. To do that, we need to keep hitting /_stats and store results in a couchdb database.

stats_copy.py does just that.

At this point it’s possible to write several views, each focused on a particular metric. See the documentation for more details.

questions, comments and suggestions welcome

Category:Uncategorized | Comment (0)

generating SVG charts with couchdb

Tuesday, 8. December 2009 14:28

In this article I describe how I got couchdb to produce SVG charts using list functions

This post is long, so I’ll report the results first:


group_level=1
yearly averages
svgpng

group_level=2
monthly averages
svgpng

group_level=3
daily values
svgpng
 

Now go and read how I did it:

  1. generate some test data
  2. upload test data to couchdb
  3. create and manage a design document with couchapp
  4. write a simple view with map/reduce
  5. write a _list function and render the charts!
  6. Conclusions
Apache CouchDB is a document-oriented database server, accessible via a RESTful JSON API. It has some advanced features, such as the ability to write ‘views’ in a map/reduce fashion and to further transform the results using javascript. It’s a young but very promising project.

Try this at home

You can browse browse or download all code discussed here. All comments and corrections are welcome.

Generate some test data

To get started with this exploration we need some data to render, and a quick way to
visualize it before our application is ready. This Python script generates a series of data points
that simulate the goings of someone’s bank account.

# test_data.py. Usage: python test_data.py <simulation_length>
import sys
import random

days = int(sys.argv[1])
savings = 10000
pay = 2000
for i in range(0, days):
	if ( i%30 == 0):
		savings = savings + pay
	savings = savings - random.randint(0, pay/16) - 2
	print i, (int(savings))

Use the script to generate a sample set with 3000 points:

$ python test_data.py 3000 > test_data.txt
$ cat test_data.txt
0 11947
1 11882
2 11813
...

Our final output will be similar to a line chart made with some bash and gnuplot:

#!/bin/sh
# gnuplot.sh generates a plot of a series piped in stdin
(echo -e "set terminal png size 750, 500\nplot \"-\" using 1:2 with lines notitle"
cat -
echo -e "end") | gnuplot
$ cat test_data.txt | sh gnuplot.sh > test_data.png

Upload test data data to couchdb

We need our data in json format so that it can be uploaded to couchdb. This python scripts converts
each input line to a json object. Each object will become a document in couchdb. All lines are collected in the ‘docs’ array, to make the output compatible with couchdb bulk document api. It also adds a tag to each document, so it’s easier to upload and manage multiple datasets.

# data_to_json.py. builds json output suitable for couchdb bulk operations
import sys
import datetime
date = datetime.datetime(2000, 01, 01)
tag = sys.argv[1]
print '{"docs":['
for line in sys.stdin:
	day, value = line.strip().split(' ')
	datestr = (date + datetime.timedelta(int(day))).strftime("%Y-%m-%d")
	if (day <> "0"): print ","
	sys.stdout.write('{"tag":"%s", "date":"%s", "amount":%s}'%(tag, datestr, value)),
print '\n]}',
$ cat test_data.txt | python data_to_json.py test-data > test_data.json
$ cat test_data.json
{"docs":[
{"tag":"test-data", "date":"2000-01-01", "amount":11896},
{"tag":"test-data", "date":"2000-01-02", "amount":11876},
....
{"tag":"test-data", "date":"2008-03-17", "amount":18703},
{"tag":"test-data", "date":"2008-03-18", "amount":18643}
]}

Create a new database with name svg-charts-demo

$ curl -i -X PUT http://localhost:5984/svg-charts-demo/
HTTP/1.1 201 Created
...
{"ok":true}

Upload the test data

$ curl -i -d @test_data.json -X POST http://localhost:5984/svg-charts-demo/_bulk_docs
HTTP/1.1 100 Continue

HTTP/1.1 201 Created
....

Verify that 3000 documents are in the database.

$ curl http://localhost:5984/svg-charts-demo/_all_docs?limit=0
{"total_rows":3000,"offset":3000,"rows":[]}

Create and manage a design document with couchapp

Design documents are special couchdb documents that contain application code such as views and lists.
CouchApp is a set of scripts that makes it easy to create and manage design documents.

In most cases installing couchapp is matter of one command. If you have any problems or want to know more, visit Managing Design Documents on the Definitive Guide.

$ easy_install -U couchapp

This command creates a new couchapp called svg-charts and installs it in couchdb

$ couchapp generate svg-charts

$ ls svg-charts/
_attachments  _id  couchapp.json  lists  shows  updates  vendor  views

$ couchapp push svg-charts http://localhost:5984/svg-charts-demo/
[INFO] Visit your CouchApp here:

http://localhost:5984/svg-charts-demo/_design/svg-charts/index.html

Write a simple view with map/reduce

This view will enable us to group the test data year, month or day and see the average
for each group.

// map.js
// key is array representing a date [year][month][day]
// value is each doc amount field (a number)
function(doc) {
	// dates are stored in the doc as 'yyyy-mm-dd'
	emit(doc.date.split('-'), doc.amount);
}
// reduce.js
// this reduce function returns an array of objects
// {tot:total_value_for_group, count:elements_in_the_group}
// clients can than do tot/count to get the average for the group
// Keys are arrays [year][month][day], so count will always be 1 when group_level=3
function(keys, values, rereduce) {
	if (rereduce) {
		var result = {tot:0, count:0};
		for (var idx in values) {
			result.tot += values[idx].tot;
			result.count += values[idx].count;
		}
		return result;
	}
	else {
		var result = {tot:sum(values), count:values.length};
		return result;
	}
}

Update the design document and test the different groupings

$ couchapp push svg-charts http://localhost:5984/svg-charts-demo/

Call the view with group_level=1 to get the data grouped by year

$ curl http://localhost:5984/svg-charts-demo/_design/svg-charts/_view/by_date?group_level=1
{"rows":[
{"key":["2000"],"value":{"tot":4247068,"count":366}},
...
{"key":["2008"],"value":{"tot":1529286,"count":78}}
]}

Call the view with roup_level=2 to get the data grouped by month

$ curl http://localhost:5984/svg-charts-demo/_design/svg-charts/_view/by_date?group_level=2
{"rows":[
{"key":["2000","01"],"value":{"tot":343578,"count":31}},
{"key":["2000","06"],"value":{"tot":345282,"count":30}},
...

Call the view with roup_level=3 to get the data grouped by day. As all the keys are different at the third level, this returns a single row for each document.

$ curl -s http://localhost:5984/svg-charts-demo/_design/svg-charts/_view/by_date?group_level=3
{"rows":[
{"key":["2000","01","01"],"value":{"tot":11896,"count":1}},
{"key":["2000","01","04"],"value":{"tot":11747,"count":1}},
...

Same as above but limiting the response to a range of days

$ curl -s 'http://localhost:5984/svg-charts-demo/_design/svg-charts/_view/by_date?group_level=3
&startkey=\["2008","01","01"\]&endkey=\["2008","01","04"\]'
{"rows":[
{"key":["2008","01","01"],"value":{"tot":20050,"count":1}},
{"key":["2008","01","02"],"value":{"tot":20019,"count":1}},
{"key":["2008","01","03"],"value":{"tot":19974,"count":1}},
{"key":["2008","01","04"],"value":{"tot":19878,"count":1}}
]}

Write a _list function and render the charts!

function(head, req) {
	start({"headers":{"Content-Type" : "image/svg+xml"}});

	// some utility functions that print svg elements
	function svg(width, height) {
		return '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"'+
		' style="fill:black"'+
		' width="'+width+'" height="'+height+'">\n';
	}
	function line(x1, y1, x2, y2, color) {
		return '<line x1="'+x1+'" y1="'+y1+'" x2="'+x2+'" y2="'+y2+'"
			style="stroke-width: 0.2; stroke:'+color+'"/>\n';
	}
	function rect(x, y, width, height, color, fill) {
		return '<rect x="'+x+'" y="'+y+'" width="'+width+'" height="'+height+'"
			style="fill:'+fill+'; stroke:'+color+'"/>\n';
	}
	function text(x,y, text) {
		return '<text x="'+x+'" y="'+y+'" font-size="11"
			font-family="sans-serif">'+text+'</text>\n';
	}

	// import query parameters
	var x_size = req.query.width || 750;
	var y_size = req.query.height || 500;
	var level = parseInt(req.query.group_level);

	// find max and min values
	// collect values and labels
	var y_max = null;
	var y_min = null;
	var values = [];
	var labels = [];
	var count = 0;
	while(row = getRow()) {
		var value = Math.ceil(row.value.tot/row.value.count);
		if (y_max==null || value>y_max) { y_max=value; }
		if (y_min==null || value<y_min) { y_min=value; }
		values[count] = value;
		labels[count] = row.key.join('-');
		count++;
	}
	// calculate scalig factors
	var in_width = x_size-(2*pad);
	var in_height = y_size-(2*pad);
	var in_x_scale = in_width/count;
	var in_y_scale = in_height/(y_max-y_min);

	// free space surrounding the actual chart
	var pad = Math.round(y_size/12);

	send('<?xml version="1.0"?>');
	send(svg(x_size, y_size));

	// background box
	send(rect(1,1, x_size, y_size, '#C6F1C7', '#C6F1C7'));

	// chart container box
	send(rect(pad,pad, x_size-(2*pad), y_size-(2*pad), 'black','white'));

	// draw labels and grid
	var y_base = y_size - pad;
	var lastx = 0;
	var lasty = 0;
	for(var i=0; i<count; i++) {
		var x = pad+Math.round(i*in_x_scale);
		if (i==0 || x-lastx > (30+12*level)) {
			send(line(x, y_base+(pad/2), x, pad,'gray'));
			send(text(x+3, y_base + (pad/2), labels[i]));
			lastx = x;
		}
		var y = Math.round(y_base - ( (values[i]-y_min) * in_y_scale));
		if (i==0 || lasty-y > 15) {
			send(line(5, y, pad+in_width, y,'gray'));
			send(text(5, y-2, values[i]));
			lasty = y;
		}
	}
	// draw the actual chart
	send('<polyline style="stroke:black; stroke-width: '+ (4-level) +'; fill: none;" points="');
	for(var i=0; i<count; i++) {
		if (i>0) send(',\n');
		var x = pad+Math.round(i*in_x_scale);
		var y = Math.round(y_base - ( (values[i]-y_min) * in_y_scale));
		send( x + ' ' + y);
	}
	send('"/>');

	send('</svg>');
}


Update couchapp, and execute the list function ‘chart-line’ against the view ‘by_date’.
Use different group_level settings, to obtain different charts:

curl http://localhost:5984/svg-charts-demo/_design/svg-charts/\
_list/chart-line/by_date?group_level=3 > chart-line_level-3.svg

curl http://localhost:5984/svg-charts-demo/_design/\
_list/chart-line/by_date?group_level=2 > chart-line_level-2.svg

curl http://localhost:5984/svg-charts-demo/_design/\
_list/chart-line/by_date?group_level=1 > chart-line_level-1.svg

group_level=1
yearly averages
svgpng

group_level=2
monthly averages
svgpng

group_level=3
daily values
svgpng
 

Concusions

It worked.

I didn’t expect to use a single list function for all grouping levels. I’m particularly happy of how it worked out, and even more considering
that the whole thing is about 100 lines of code.

The output isn’t too nice, but I think I can be made presentable with under 500 lines of code and some effort.

Couchdb is always a pleasure to work with and it goas a long way in minimizing “Time To something Done”.

Category:Uncategorized | Comments (8)

Learning Closure: managing dependencies and compiling

Friday, 4. December 2009 10:42

Google’s closure library offers semantics and tools to manage dependencies between modules. This is specially useful when building single-page javascript applications.

To find out how smoothly this works, I’ll build a simple javascript application that given a string, displays its MD5 hash. We’ll

If you have used jQuery you’ll find that this code is verbose and painfully similar to java.

To ease your mind, think that structured libraries like Closure and YUI tend to promote more readable and maintainable code (at the expense of coolness).

Closure and YUI are a better fit when the goal is to build a complex application.

Try this at home

See the demo of the sample application discussed below.
You can browse browse or download all source code discussed here, including all 3rd party libraries. I welcome all comments and corrections.

Project setup

Create an empty directory (i called mine goog-dependencies-example), enter it and download all the stuff

You should now have a structure like this:

goog-dependencies-example\
  |-- closure-templates\
  |-- closure-library\
  |-- jshash-2.2\

Publish this directory on a web server.

During development, I use
lighttpd with this minimal configuration file to quickly publish just a directory. Save the file in the directory you want to publish, run lighttpd -D -f lhttpd-minimal.txt and browse to http://localhost:3030/. Lighttpd is available on ubuntu, macos (via macports) and windows (via cygwin).

UI: template

We need a ui component with a textarea to input some text, a button to execute and and a textarea to see the results. Using closure template is definitely overkill here, but it’s useful to demonstrate how to load templates a dependencies.

Create the file md5.ui.template.soy

{namespace net.caprazzi.md5.ui.template}

/**
 * md5 ui template
 * To retrieve its contents, invoke the function
 * net.caprazzi.md5.template.ui.main()
 */
{template .main}
<div class="md5-ui">
	<textarea class="md5-input"></textarea><br/>
	<button class="md5-action">Hash It!</button><br/>
	<textarea class="md5-output"></textarea><br/>
</div>
{/template}

And execute this command to compile it to a javascript file:

$ java -jar closure-templates/SoyToJsSrcCompiler.jar \
	--shouldProvideRequireSoyNamespaces \
	--outputPathFormat js/md5.ui.template.js md5.ui.template.soy
// This file was automatically generated from md5.ui.template.soy.
// Please don't edit this file by hand.

goog.provide('net.caprazzi.md5.ui.template');

goog.require('soy');
goog.require('soy.StringBuilder');

net.caprazzi.md5.ui.template.main = function(opt_data, opt_sb) {
  var output = opt_sb || new soy.StringBuilder();
  output.append('<div class="md5-ui">
	<textarea class="md5-input"></textarea><br/>
	<button class="md5-action">Hash It!</button><br/>
	<textarea class="md5-output"></textarea><br/></div>');
  if (!opt_sb) return output.toString();
};

UI: javascript

Now let’s write some code that hashes the contents of the first textarea when the button is clicked.
Name the file js/md5.ui.js.

goog.provide('net.caprazzi.md5.ui');

// NOTE the inclusion of our template
goog.require('net.caprazzi.md5.ui.template');

// NOTE the inclusion of jshash
// as defined in third_party_deps.js
goog.require('jshash.md5');

goog.require('goog.events');
goog.require('goog.dom');

net.caprazzi.md5.Ui = function(parent) {
	this.parent = parent;
}

net.caprazzi.md5.Ui.prototype.render = function() {
	var html = net.caprazzi.md5.ui.template.main();
	this.parent.innerHTML = html;
	this.input = goog.dom.$$('textarea', 'md5-input', this.parent)[0];
	this.button = goog.dom.$$('button', 'md5-action', this.parent)[0];
	this.output = goog.dom.$$('textarea', 'md5-output', this.parent)[0];

	var self = this;
	goog.events.listen(this.button,
			goog.events.EventType.CLICK,
			function() { self.onButton_()});
}

net.caprazzi.md5.Ui.prototype.onButton_ = function() {
	this.output.value = hex_md5(this.input.value);
}

Application entry point

This file exposes the function main(), that will be executed at page load. It only requires the two modules that uses directly

goog.provide('net.caprazzi.md5.application');

goog.require('net.caprazzi.md5.ui');
goog.require('goog.dom');

net.caprazzi.md5.application.main = function() {
	var container = document.getElementById('md5-ui-container');
	var ui = new net.caprazzi.md5.Ui(container);
	ui.render();
}

deps.js: the dependencies file

Calcdeps.py is a script that comes with closure library. It parses the source files in search of goog.require() and goog.provide() declarations. It then uses those declarations to build a dependency tree.

The dependency tree is stored in a file as a list of goog.addDependency calls. Each calls describes a file and the modules it provieds and requires. This line declares that md5.component.js will provide ‘net.caprazzi.md5′ and require ‘net.caprazzi.md5.template’ and others:


goog.addDependency('js/md5.component.js',
    ['net.caprazzi.md5'],
    ['net.caprazzi.md5.template', 'goog.events', 'goog.dom', 'jshash']);

Run this command in the root of the project to generate the deps.js all the dependecies (including google’s). This may take a while.

$ python closure-library/closure/bin/calcdeps.py \
    -p closure-library/closure \
    -p closure-library/third_party \
    -p closure-templates \
    -p js \
    -i js /md5.* \
    -o deps \
> deps.js

The option ‘-o deps’ tells calcdeps.py to output a dependency tree. The other options specify the source directories and files. Spend a moment to review the contents of the resulting file.

Managing the third party library ‘md5.js’

As I explained above, calcdeps uses special declarations in the javascript files to make sense of the dependencies. We know that by ‘jshash.md5′ i mean ‘jshash-2.2/md5.js’ but calcdeps could only figure this out if the file included a correct goog.require statement.

At this point we may just add the line to the file and go on with our lives. But we all agree that editing 3rd party libraries is not a good practice.

I maintain a text file ‘extradeps.txt’ where each line associates a module to a file, in this case the contents are

jshash.md5 jshash-2.2/md5-min.js

Then a simple python script parses that file and generates the missing addDependency() statements:

import sys
for mod, path in [  line.strip().split(' ') for line in file(sys.argv[1]) ]:
	print "goog.addDependency('%s',['%s'],[]);" % (path, mod)

Execute the script to see what the output looks like. Then remember to concatenate its output to deps.js each time your rebuild:

$ python fix_deps_file.py extradeps.txt >> deps.js

in dev: index_dev.html

The last piece of the puzzle is the one that holds everything together: the html

<html>
	<head>
		<title>Hash me, hash me</title>
		<script>
			CLOSURE_NO_DEPS=true;
			CLOSURE_BASE_PATH="./";
		</script>
		<script src="closure-library/closure/goog/base.js"></script>
		<script src="deps.js"></script>
		<script>
			goog.require('net.caprazzi.md5.application');
		</script>
	</head>
	<body onload="net.caprazzi.md5.application.main();">
		<h3>Hash me, hash me</h3>
		<div id="md5-ui-container"></div>
	</body>
</html>

Note the two global directives before the inclusion of base.js:

  • CLOSURE_NO_DEPS stops closure from loading its own deps.js
  • CLOSURE_BASE_PATH tells closure file loader how to build the script urls.

Note that the html file only imports closure’s base.js and deps.js then invokes directly code that comes from md5.ui.js

Navigate to index.html, the application should be working.

Use firebug or another network monitor, to see how the browser is downloading many different files to satisfy all the dependencies. This is the perfect behaviour for development, because separate files are easier to debug.

going live: the single js file and index_live.html

Loading many small javascript files is an evil practice in production.
We definitely want to have all our javascript files concatenated in one big blob.

I previously used calcdeps.py with the option ‘-o deps’ to build a dependency tree. Calcdeps can also concatenate all your dependancies right away, using the option ‘-o script’. Unfortunately if you try to run it against our code, it will terminate with an exception (

Exception: Missing provider for (jshash.md5)

. Clearly, again, the dependency script doesn’t know which files provides that module

Python to the rescue again: we’ll reuse the extradeps.txt file with a new python script that takes all the external deps and puts them in a file along with the correct goog.provide() statements.

import sys
mods = [  line.strip().split(' ') for line in file(sys.argv[1]) ];
for mod, path in mods:
	print "goog.provide('%s');" % (mod)
for mod, path in mods:
	for line in file(path):
		print line

Running this script produces a calcdeps-friendly javascript file:

$ mkdir tmp/
$ python concat_extra_deps.py extradeps.txt > tmp/extra.js

We can now tell calcdeps to look into tmp/ to find what’s needed to build our big target file:

$ python closure-library/closure/bin/calcdeps.py \
	-p closure-library \
	-p tmp \
	-p js \
	-p closure-templates \
 	-i js/md5.application.js \
    -o script \
> md5_application.js

Now we can produce the final artifact and go live with index_live.html:

<html>
	<head>
		<title>Hash me, hash me</title>
		<script src="md5_application.js"></script>
	</head>
	<body onload="net.caprazzi.md5.application.main();">
		<h3>Hash me, hash me</h3>
		<div id="md5-ui-container"></div>
	</body>
</html>

Conclusions

I’ve done this exercise to understand if the closure’s dependency management would be usable in a production environment.

There was some initial misunderstanding between calcdeps and me and I had to look at base.js to understand what was going on and to find out about the global directives that influence the loader.

The syntax is neat and serves the double purpose of managing namespaces and dependencies.

My impression is that at least the goog.require/provide syntax and the calcdeps.py script can be used in production with confidence.

Closure dosen’t allow to specify css files as dependencies, while it’s possible with YUI3 (that sports a cool dependency management mechanism of his own).

As an exercise you can think about adding a compilation step using google closure compiler.

-teo

Category:javascript | Comments (1)

Book: Programming Collective Intelligence

Wednesday, 2. December 2009 18:47

Programming books are seldom page turners, even for geeks like us. And programming books often fail to go beyond the technology. Programming books are rarely as good as this one. Allow me to digress for a moment.

Online there is a lot of data about you. And me. Our reactions have been logged, our desires noted. You know where I am now. I know your status. Ok, I’m being dramatic, but bear with me. I have a point.

All this data tells one story and million stories in their gritty details. And since past behaviour is a good predictor of future behaviour, I can read your vekernel_triclry future in those million stories.

Not so easy, of course. All those records are generated by humans and computers. It’s cybernetic data, its scale and complexity are beyond belief. We can only make sense of that data using computers.

Back to the topic. This very thick yet enjoyable book is a programmer’s journey in the world of machine learning and statistics.

The author, Toby Segaran, is brilliant. He will hold your hand while describing many algorithms an techniques. He’ll tell you just enough theory to make sense of what’s going on. And then, plain simple python, down with the implementation.

Interested in building and thinking about a recommendation engine? What about picking a good music generessolution to a proble, amongst millions? Curious about genetic programming? Wanna have your own spam filter?

The last great thing about this book are the example. Real world, not your usual fictional pet shop. You’ll fetch data from your delicious account, correlate live stock market data from yahoo, analyze your favorite blogs and more stuff.

Tony writing is unpretentious and the book is easy to read, but beware there is a lot of meat to digest. Don’t rush.

collective_intelligence

Category:Uncategorized | Comment (0)

Learning Closure. Ajax with goog.net.*

Thursday, 12. November 2009 0:20

Google released closure library, a javascript library good to make rich internet applications.

As this is going to be an important library in software developement, I decided to try it. ‘Ajax’ networking is a good starting point, so I looked into the package goog.net and found two promising classes, HxrIo and XhrManager, which I used for my tests.

The documentation is clear but sometimes shy about who does what. Fortunately there are plenty of links to the source code, which is well written and documented.

The interesting code is below, but there’s a more extend version of my closure-library tests.

Just hit a url without waiting for a response


// load the modules
goog.require('goog.net.XhrIo');
goog.require('goog.Uri');

// the easy way: static function call with no callback
goog.net.XhrIo.send('url/');

// using the send() method of an instance.
new goog.net.XhrIo().send('urlA'); // GET, default
new goog.net.XhrIo().send('urlB', 'POST');

// also works with an Uri object
var uri = new goog.Uri('urlC');
new goog.net.XhrIo().send(uri, 'PUT');

// no http method validation, this is a valid call
new goog.net.XhrIo().send('urlF', 'XXXX');

// send some data with the POST, works as expected
new goog.net.XhrIo().send('urlG', 'POST', 'p1=v1&p2=v2);

// ATTENTION data will not be attached to a GET
// so this call won't do what you may expect.
new goog.net.XhrIo().send('urlH', 'GET', 'p1=v1&p2=v2');

Request data from the server


// an html file as text
goog.net.XhrIo.send('sample.html', function(event) {
	var text = event.target.getResponseText());
});

// an xml file as document object
goog.net.XhrIo.send('sample.xml', function(event) {
	var document = event.target.getResponseXml());
});

// a json file as javascript object
goog.net.XhrIo.send('sample.json.js', function(event) {
	var data = event.target.getResponseJson());
});

// spot the 404
goog.net.XhrIo.send('gone_fishin', function(event) {
	var success = event.target.isSuccess()); // false
	var status = event.target.getStatus()); // 404
	var statusText = event.target.getStatusText()); // not found
});

// !!! exceptions in the callback are swallowed, so check your conditions
// (there is interesting error handling stuff in goog.debug.*)
goog.net.XhrIo.send('gone_fishin.js', function(event) {
	// I expected some data, but the file is not there
	// and this will raise an exception...
	var data = event.target.getResponseJson();
	var field_data = data.field;
	// ...so this statement will not execute...
	alert('bonk');
	// ...BUT you won't see anything in your console.
});

// a 404 using an XhrIO instance and closure event management;
var io = new goog.net.XhrIo();
goog.events.listen(io, goog.net.EventType.COMPLETE, function(event) {
	var status = event.target.getStatus()); // 404
});
io.send('gone_fishin');

Using the connection manager


goog.require('goog.net.XhrManager');

// goog.net.XhrManager(opt_maxRetries, opt_headers, opt_minCount, opt_maxCount, opt_timeoutInterval)
var mgr = new goog.net.XhrManager(2, null, 0, 2);
goog.events.listen(mgr, goog.net.EventType.COMPLETE, function(event) {
	// this is fired once for each send(), even if they are retried
	var xhr = event.xhrIo;
});
mgr.send('id_one','closure.js');
mgr.send('id_two','/urlTwo');
mgr.send('id_three','/urlThree');
mgr.send('id_four','/urlFour');

// reusing an id before the request is complete, causes an exception...
try { mgr.send('id_one','/x'); }
catch (e) { /*[goog.net.XhrManager] ID in use*/ }

// ...unless the connection is aborted
mgr.abort('id_one');
mgr.send('id_one','/x');

mgr.send('other_id','/someurl', null, null, null, function(event) {
	// it's ok to reuse an id an after the request completed
	mgr.send('other_id', '/fool');
});

Category:Uncategorized | Comments (4)

book: forms that work

Friday, 23. October 2009 15:13

Forms that Work: Designing Web Forms for Usability

There is no html in this book, it’s not about programming forms. And that’s why I liked it.

This short book clarifies what good form design is about, in plain english. Plenty of images help, too.
I’m still not too good at designing forms but at least now I know the name of the game.

-teo

forms_that_work

Category:book review forms design | Comments (1)

book: don’t make me think

Thursday, 15. October 2009 14:14

Don’t Make Me Think!: A Common Sense Approach to Web Usability

Delightful one-night read. Some good common sense advice on web design and two brilliant chapters on usability testing on a budget.

common sense is underrated

Category:Uncategorized | Comment (0)

Get Adobe Flash playerPlugin by wpburn.com wordpress themes