extract latest dive code

commit 309566376351bb3761163f83c39f2dd85f3007f0
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Tue Apr 29 10:52:43 2014 -0300

    implement deferred cellf type on Dive

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 5e17696..7f7f0c3 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -309,6 +309,9 @@ var dirty_dependants = function(dependants, triggererContext) {
             switch (ctx.type) {
             case 'lazy':
                 break;
+            case 'defer':
+                setTimeout(ctx.update, 0);
+                break;
             default:
                 ctx.update();
                 break;
@@ -371,9 +374,15 @@ Dive.cellf = function() {
     if (typeof context !== 'function') throw new Error(
         'Dive.cellf expected a function as sole argument');

+
     context.$dive_ctx = generateContext(type);
-    var computed = context.call(context),
-        dependants = [];
+    var computed, dependants = [];
+
+    if (type == 'defer')
+        setTimeout(function() {
+            computed = context.call(context);
+        });
+    else computed = context.call(context);

     var update = function() {
         register_dep(arguments.callee.caller.$dive_ctx, dependants);

commit 2f7e01c07492b143d8118a009175cdefc7eb802a
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Apr 23 17:29:27 2014 -0300

    create Dive.autorun alias for Dive.cellf/eager

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 47c69de..5e17696 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -390,6 +390,8 @@ Dive.cellf = function() {
     return update;
 };

+Dive.autorun = function(context) { return Dive.cellf('eager', context); };
+
 for (var p = 0; p < plugins.length; p++) {
     if (typeof plugins[p] === 'function') plugins[p]();
 }

commit ccd074fd694f91bfbc77ab569eb3ac718b33961a
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Tue Apr 22 20:37:44 2014 -0300

    implement 'style' option in Dive

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index cb650c3..47c69de 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -113,6 +113,13 @@ var processOptions = function(el, options, content) {
             if (options.data.hasOwnProperty(datum))
                 el.setAttribute('data-' + datum, options.data[datum]);

+    var styleString = '';
+    if (options.style)
+        for (var style in options.style)
+            if (options.style.hasOwnProperty(style))
+                styleString += style + ': ' + options.style[style] + '; ';
+    if (styleString) el.setAttribute('style', styleString);
+
     var attrs = ['href', 'value', 'type'];
     for (i = 0; i < attrs.length; i++)
         if (options[attrs[i]]) el.setAttribute(attrs[i], options[attrs[i]]);
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index b344537..5981caa 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -150,7 +150,7 @@ describe('Dive', function () {
                .outerHTML).toEqual('<b><div></div>a<span></span></b>');
     });

-    it("sets attributes on an element through the 'attr' option", function() {
+    it("sets attributes on an element through options", function() {
         expect(Dive.b({ attr: { value: 1 } }).outerHTML)
             .toEqual('<b value="1"></b>');
         var el = Dive.b({ attr: { value: '1', class: 'hey you' },
@@ -158,6 +158,14 @@ describe('Dive', function () {
         expect(el.className).toEqual('hey you');
         expect(el.getAttribute('value')).toEqual('1');
         expect(el.getAttribute('data-hey')).toEqual('2');
+
+        var el1 = Dive.b({ attr: { value: '1' }, value: '2', href: '3'});
+        expect(el1.getAttribute('value')).toEqual('2');
+        expect(el1.getAttribute('href')).toEqual('3');
+
+        var el2 = Dive.b({ style: { float: 'left', overflow: 'auto' }});
+        expect(el2.getAttribute('style'))
+            .toEqual('float: left; overflow: auto; ');
     });

     it("binds partial options to a Dive.div function", function() {

commit 65009c313ad483584b496d80b1106f2adf69a745
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Tue Apr 22 20:35:29 2014 -0300

    fix bug from setting a Dive.cell where it is a dep

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 17dd62b..cb650c3 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -289,9 +289,13 @@ var register_dep = function(ctx, dependants) {
     }
 };

-var dirty_dependants = function(dependants) {
+var dirty_dependants = function(dependants, triggererContext) {
     for (var i = 0; i < dependants.length; i++) {
         var ctx = dependants[i];
+        // dirtying the triggerer context (if any) would set us up for
+        // a vicious cycle.
+        if (triggererContext && ctx.id === triggererContext.id)
+            continue;

         ctx.dirty = true;
         if (ctx)
@@ -315,7 +319,8 @@ Dive.cell = function(initialValue) {
             } else {
                 if (cell != setValue) {
                     cell = setValue;
-                    dirty_dependants(dependants);
+                    dirty_dependants(dependants,
+                                     arguments.callee.caller.$dive_ctx);
                 }
                 return setValue;
             }
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index 4ea8047..b344537 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -237,6 +237,13 @@ describe('Dive', function () {
         expect(cell2E()).toEqual('z9');
     });

+    it("registers repeated cell deps", function() {
+        var cell = Dive.cell(1),
+            cellf = Dive.cellf(function() { return cell() + cell(); });
+
+        expect(cellf()).toEqual(2);
+    });
+
     it("registers nested function cell deps", function() {
         var cell1 = Dive.cell(1),
             cell2 = Dive.cell(2),

commit 4877c1d7f7c6bcfaec87f835d0a040e9ec6b7eae
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Apr 16 16:57:10 2014 -0300

    add some more semantic Dive elements

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index eac1cb7..17dd62b 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -1,7 +1,9 @@
 (function() {
 var elements = ['div', 'span', 'a', 'em', 'i', 'b', 'strong',
                 'table', 'thead', 'tbody', 'tr', 'td', 'th',
-                'input', 'label', 'select', 'option', 'form'],
+                'input', 'label', 'select', 'option', 'form',
+                'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'big',
+                'small', 'sup', 'sub', 'content', 'aside'],
     selector_syntax_error = new Error(
         "Dive: expected selector-syntax string as first argument to Dive()"),
     extensions,

commit c96d1bb9846d0449f61838085a86f604b29ad2fa
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Apr 16 15:50:14 2014 -0300

    switch Dive.cellf overloaded argument order

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 29bf8ec..eac1cb7 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -345,7 +345,15 @@ var generateContext = function(type) {
     };
 };

-Dive.cellf = function(context, type) {
+Dive.cellf = function() {
+    var context, type;
+    if (arguments.length == 1) {
+        type = 'eager',
+        context = arguments[0];
+    } else if (arguments.length > 1) {
+        type = arguments[0];
+        context = arguments[1];
+    } else throw new Error("Dive.cellf can't take no arguments.");
     if (typeof context !== 'function') throw new Error(
         'Dive.cellf expected a function as sole argument');

diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index 5d76558..4ea8047 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -184,8 +184,7 @@ describe('Dive', function () {
     it("registers lazy cell deps. in a cell function's context", function() {
         var cell1 = Dive.cell('a');
         expect(cell1()).toEqual('a');
-        var cell1L = Dive.cellf(function() { return cell1() + 'b'; },
-                              'lazy');
+        var cell1L = Dive.cellf('lazy', function() { return cell1() + 'b'; });

         expect(cell1L()).toEqual('ab');
         cell1('b'); expect(cell1L()).toEqual('bb');
@@ -193,10 +192,10 @@ describe('Dive', function () {

         var spy = jasmine.createSpy(),
             cell2 = Dive.cell(1),
-            cell2L = Dive.cellf(function() {
+            cell2L = Dive.cellf('lazy', function() {
                 spy();
                 return cell1() + cell2();
-            }, 'lazy');
+            });

         expect(spy.callCount).toEqual(1);
         cell1('b'); cell2(4); cell1('c');

commit a1fb7076760cb9c45d56ffd8b89e551f304965cd
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Apr 16 14:31:24 2014 -0300

    implement nested dive function cell dependencies

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 5e09f5f..29bf8ec 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -350,12 +350,16 @@ Dive.cellf = function(context, type) {
         'Dive.cellf expected a function as sole argument');

     context.$dive_ctx = generateContext(type);
-    var computed = context.call(context);
+    var computed = context.call(context),
+        dependants = [];

     var update = function() {
+        register_dep(arguments.callee.caller.$dive_ctx, dependants);
+
         if (context.$dive_ctx.dirty) {
             context.$dive_ctx.dirty = false;
             computed = context.call(context);
+            dirty_dependants(dependants);
         }
         return computed;
     };
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index 0ed612d..5d76558 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -238,4 +238,20 @@ describe('Dive', function () {
         expect(cell2E()).toEqual('z9');
     });

+    it("registers nested function cell deps", function() {
+        var cell1 = Dive.cell(1),
+            cell2 = Dive.cell(2),
+            cella = Dive.cell('a'),
+            cellf1 = Dive.cellf(function() { return cell1() + 10 + cell2(); }),
+            cellf2 = Dive.cellf(function() {
+                return 'number: ' + cellf1() + ' letter: ' + cella();
+            });
+
+        expect(cellf1()).toEqual(13);
+        expect(cellf2()).toEqual('number: 13 letter: a');
+        cell1(3);
+        expect(cellf2()).toEqual('number: 15 letter: a');
+        cella('b'); cell2(4);
+        expect(cellf2()).toEqual('number: 17 letter: b');
+    });
 });

commit 084107ae7491d514d256a13b3c1acf84f036923c
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Apr 16 14:26:59 2014 -0300

    extract dive dependants-dirtying into helper

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 004df4d..5e09f5f 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -272,44 +272,48 @@ Dive.bindOptions = function() {
     };
 };

-var registerDep = function(ctx, depFor) {
+var register_dep = function(ctx, dependants) {
     if (ctx) {
         var alreadyRegistered = false;
-        for (var k = 0; k < depFor.length; k++)
-            if (ctx.id === depFor[k].id) {
+        for (var k = 0; k < dependants.length; k++)
+            if (ctx.id === dependants[k].id) {
                 alreadyRegistered = true;
                 break;
             }
         if (!alreadyRegistered) {
             //ctx.deps.push(fn);
-            depFor.push(ctx);
+            dependants.push(ctx);
         }
     }
 };

+var dirty_dependants = function(dependants) {
+    for (var i = 0; i < dependants.length; i++) {
+        var ctx = dependants[i];
+
+        ctx.dirty = true;
+        if (ctx)
+            switch (ctx.type) {
+            case 'lazy':
+                break;
+            default:
+                ctx.update();
+                break;
+            }
+    }
+};
+
 Dive.cell = function(initialValue) {
     var cell = initialValue,
-        depFor = [],
+        dependants = [],
         fn = function(setValue) {
             if (setValue == undefined) {
-                registerDep(arguments.callee.caller.$dive_ctx, depFor);
+                register_dep(arguments.callee.caller.$dive_ctx, dependants);
                 return cell;
             } else {
                 if (cell != setValue) {
                     cell = setValue;
-                    for (var i = 0; i < depFor.length; i++) {
-                        var dctx = depFor[i];
-
-                        dctx.dirty = true;
-                        if (dctx)
-                            switch (dctx.type) {
-                            case 'lazy':
-                                break;
-                            default:
-                                dctx.update();
-                                break;
-                            }
-                    }
+                    dirty_dependants(dependants);
                 }
                 return setValue;
             }

commit ad6d90e91c3fc4141a4b0c124810faaf9ef7bce1
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Apr 16 14:18:26 2014 -0300

    fix dive unique id generation concatenation bug

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index e6e7ee4..004df4d 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -329,7 +329,7 @@ var idInc = 0,
             n = stamp.length - inc.length;

         if (inc < 0) throw new Error("You've overflown Dive's id generation.");
-        return stamp + Dive.repeat(n, '0') + inc;
+        return stamp + Dive.repeat(n, '0').join('') + inc;
     };

 var generateContext = function(type) {

commit 15f5798501a1143845b0b43217ff2e6bc7c802d1
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Apr 16 14:01:33 2014 -0300

    refactor dive cell dep registering into helper

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 28b4f19..e6e7ee4 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -272,25 +272,27 @@ Dive.bindOptions = function() {
     };
 };

+var registerDep = function(ctx, depFor) {
+    if (ctx) {
+        var alreadyRegistered = false;
+        for (var k = 0; k < depFor.length; k++)
+            if (ctx.id === depFor[k].id) {
+                alreadyRegistered = true;
+                break;
+            }
+        if (!alreadyRegistered) {
+            //ctx.deps.push(fn);
+            depFor.push(ctx);
+        }
+    }
+};
+
 Dive.cell = function(initialValue) {
     var cell = initialValue,
         depFor = [],
         fn = function(setValue) {
             if (setValue == undefined) {
-                var ctx = arguments.callee.caller.$dive_ctx;
-                if (ctx) {
-                    var alreadyRegistered = false;
-                    for (var k = 0; k < depFor.length; k++)
-                        if (ctx.id === depFor[k].id) {
-                            alreadyRegistered = true;
-                            break;
-                        }
-                    if (!alreadyRegistered) {
-                        //ctx.deps.push(fn);
-                        depFor.push(ctx);
-                    }
-                }
-
+                registerDep(arguments.callee.caller.$dive_ctx, depFor);
                 return cell;
             } else {
                 if (cell != setValue) {

commit 8ac65d20eb0b359f20ac1cf06b1f55b3dcdc7bc7
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Tue Apr 15 17:26:04 2014 -0300

    update a Dive.cell iff new value is diff. from old

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 328afee..28b4f19 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -293,19 +293,21 @@ Dive.cell = function(initialValue) {

                 return cell;
             } else {
-                cell = setValue;
-                for (var i = 0; i < depFor.length; i++) {
-                    var dctx = depFor[i];
-
-                    dctx.dirty = true;
-                    if (dctx)
-                        switch (dctx.type) {
-                        case 'lazy':
-                            break;
-                        default:
-                            dctx.update();
-                            break;
-                        }
+                if (cell != setValue) {
+                    cell = setValue;
+                    for (var i = 0; i < depFor.length; i++) {
+                        var dctx = depFor[i];
+
+                        dctx.dirty = true;
+                        if (dctx)
+                            switch (dctx.type) {
+                            case 'lazy':
+                                break;
+                            default:
+                                dctx.update();
+                                break;
+                            }
+                    }
                 }
                 return setValue;
             }

commit 46b61b56a21bb083e4ee649e6a7da3e98e67d6c5
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Mon Apr 14 20:56:35 2014 -0300

    implement Dive() selector-syntax template nesting

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 7db1eaf..328afee 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -13,9 +13,20 @@ if (Dive) {
 }

 window.Dive = function() {
-    var type, first = arguments[0], args = [];
+    var first = arguments[0];
     if (typeof first !== 'string') throw selector_syntax_error;

+    var i, type, opts = {},
+        args = [], nestArgs = [],
+        nest = first.indexOf(' > ');
+
+    if (nest != -1) {
+        for (i = 1; i < arguments.length; i++) nestArgs.push(arguments[i]);
+        opts.appendContent = Dive.apply(
+            undefined, [first.substr(nest + 3)].concat(nestArgs));
+        first = first.substr(0, nest);
+    } else for (i = 1; i < arguments.length; i++) args.push(arguments[i]);
+
     var idx = first.search(/[\.#]/);
     if (idx == -1) type = first;
     else if (idx == 0) type = 'div';
@@ -24,9 +35,9 @@ window.Dive = function() {
         first = first.substr(idx);
     }

+    opts.css = first;
     if (elements.indexOf(type) != -1) {
-        for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
-        return createElement(type, args, { css: first });
+        return createElement(type, args , opts);
     }
     else throw selector_syntax_error;
 };
@@ -80,13 +91,14 @@ Dive.processOptionsHook = function(hook) {
 };

 var processOptions = function(el, options, content) {
-    for (var h = 0; h < processOptionsHooks.length; h++)
-        processOptionsHooks[h](el, options, content);
+    var i;
+    for (i = 0; i < processOptionsHooks.length; i++)
+        processOptionsHooks[i](el, options, content);

     if (options.hasOwnProperty('when') && !options.when) return null;
     if (options.prependContent) content.unshift(options.prependContent);
     if (options.appendContent) content.push(options.appendContent);
-    for (var i = 0; i < content.length; i++)
+    for (i = 0; i < content.length; i++)
         appendContent(el, content[i]);

     if (options.attr)
@@ -99,8 +111,10 @@ var processOptions = function(el, options, content) {
             if (options.data.hasOwnProperty(datum))
                 el.setAttribute('data-' + datum, options.data[datum]);

-    if (options.href) el.setAttribute('href', options.href);
-    if (options.value) el.setAttribute('value', options.value);
+    var attrs = ['href', 'value', 'type'];
+    for (i = 0; i < attrs.length; i++)
+        if (options[attrs[i]]) el.setAttribute(attrs[i], options[attrs[i]]);
+
     if (options.css) {
         var cssParser = new Holmes.CssConfigParser(options.css),
             classes = cssParser.classList(),
@@ -147,9 +161,6 @@ Dive.map = function(f, coll) {
     if (coll == undefined)
         return function(coll2) { return Dive.map(f, coll2); };

-    var caller = arguments.callee.caller,
-        ctx = caller.$dive_ctx;
-
     var results = [];
     for (var i = 0; i < coll.length; i++) {
         f.$index = i;
@@ -266,14 +277,14 @@ Dive.cell = function(initialValue) {
         depFor = [],
         fn = function(setValue) {
             if (setValue == undefined) {
-                var caller = arguments.callee.caller,
-                    ctx = caller.$dive_ctx;
-
+                var ctx = arguments.callee.caller.$dive_ctx;
                 if (ctx) {
                     var alreadyRegistered = false;
                     for (var k = 0; k < depFor.length; k++)
-                        if (ctx.id === depFor[k].id)
+                        if (ctx.id === depFor[k].id) {
                             alreadyRegistered = true;
+                            break;
+                        }
                     if (!alreadyRegistered) {
                         //ctx.deps.push(fn);
                         depFor.push(ctx);
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index cb96311..0ed612d 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -30,6 +30,11 @@ describe('Dive', function () {
         expect(Dive('b.to.be').outerHTML).toEqual('<b class="to be"></b>');
         expect(Dive('b.to#be', { css: '.hey#you' }).outerHTML)
             .toEqual('<b class="hey to" id="you be"></b>');
+
+        expect(Dive('b.to > #be > a.you', { css: '.hey' }, 'hello')
+               .outerHTML).toEqual('<b class="to"><div id="be">' +
+                                   '<a class="hey you">hello</a>' +
+                                   '</div></b>');
     });

     it("weeds out null elements", function() {

commit 453bc3c10fe40f0d536598ac9b5ee37c5e5617c8
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Mon Apr 14 15:34:40 2014 -0300

    implement Dive eager cells

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 3605f4b..7db1eaf 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -272,11 +272,11 @@ Dive.cell = function(initialValue) {
                 if (ctx) {
                     var alreadyRegistered = false;
                     for (var k = 0; k < depFor.length; k++)
-                        if (ctx.id === depFor[k].$dive_ctx.id)
+                        if (ctx.id === depFor[k].id)
                             alreadyRegistered = true;
                     if (!alreadyRegistered) {
                         //ctx.deps.push(fn);
-                        depFor.push(caller);
+                        depFor.push(ctx);
                     }
                 }

@@ -284,16 +284,15 @@ Dive.cell = function(initialValue) {
             } else {
                 cell = setValue;
                 for (var i = 0; i < depFor.length; i++) {
-                    var depfor = depFor[i],
-                        dctx = depfor.$dive_ctx;
+                    var dctx = depFor[i];

+                    dctx.dirty = true;
                     if (dctx)
                         switch (dctx.type) {
                         case 'lazy':
-                            dctx.dirty = true;
                             break;
                         default:
-                            //depfor.update.call(depfor.update, true);
+                            dctx.update();
                             break;
                         }
                 }
@@ -321,9 +320,9 @@ var idInc = 0,
 var generateContext = function(type) {
     return {
         id: genId(),
+        //deps: [],
         type: type,
-        dirty: false,
-        deps: []
+        dirty: false
     };
 };

@@ -334,14 +333,16 @@ Dive.cellf = function(context, type) {
     context.$dive_ctx = generateContext(type);
     var computed = context.call(context);

-    return function(debug) {
+    var update = function() {
         if (context.$dive_ctx.dirty) {
-            //if (debug) console.info('hey', hey);
             context.$dive_ctx.dirty = false;
             computed = context.call(context);
         }
         return computed;
     };
+
+    context.$dive_ctx.update = update;
+    return update;
 };

 for (var p = 0; p < plugins.length; p++) {
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index a59eade..cb96311 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -203,4 +203,34 @@ describe('Dive', function () {
         cell1('z'); cell2(9);
         expect(cell2L()).toEqual('z9');
     });
+
+    it("registers eager cell deps. in a cell function's context", function() {
+        var cell1 = Dive.cell('a');
+        expect(cell1()).toEqual('a');
+        var cell1E = Dive.cellf(function() { return cell1() + 'b'; });
+
+        expect(cell1E()).toEqual('ab');
+        cell1('b'); expect(cell1E()).toEqual('bb');
+        cell1('a'); expect(cell1E()).toEqual('ab');
+
+        var spy = jasmine.createSpy(),
+            cell2 = Dive.cell(1),
+            cell2E = Dive.cellf(function() {
+                spy();
+                return cell1() + cell2();
+            });
+
+        expect(spy.callCount).toEqual(1);
+        cell1('b'); cell2(4); cell1('c');
+        expect(spy.callCount).toEqual(4);
+        expect(cell2E(1)).toEqual('c4');
+        expect(spy.callCount).toEqual(4);
+        cell2E(2);
+        expect(spy.callCount).toEqual(4);
+
+        cell1('z');
+        cell2(9);
+        expect(cell2E()).toEqual('z9');
+    });
+
 });

commit dc6f2a6772803afb1cdea52b55d19dbf2e0c4928
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Fri Apr 11 19:35:17 2014 -0300

    implement Dive lazy cells and plugin/ext hooks

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index d714938..3605f4b 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -4,9 +4,13 @@ var elements = ['div', 'span', 'a', 'em', 'i', 'b', 'strong',
                 'input', 'label', 'select', 'option', 'form'],
     selector_syntax_error = new Error(
         "Dive: expected selector-syntax string as first argument to Dive()"),
-    plugins;
+    extensions,
+    plugins = [];

-if (Dive) plugins = Dive;
+if (Dive) {
+    extensions = Dive;
+    if (Dive.plugins) plugins = Dive.plugins;
+}

 window.Dive = function() {
     var type, first = arguments[0], args = [];
@@ -51,10 +55,10 @@ Dive.into = function(dest_, orig_, mergeStrategy) {
     return dest;
 };

-Dive.into(Dive, plugins);
+Dive.into(Dive, extensions);

 var appendContent = function(parent, content) {
-    if (content == undefined || content == null || typeof content == "boolean")
+    if (content == undefined || content == null || typeof content === "boolean")
         return;
     else if (content.nodeName) parent.appendChild(content);
     else if (content instanceof Array)
@@ -64,11 +68,21 @@ var appendContent = function(parent, content) {
 };

 var gotOptions = function(arg) {
-    return arg && typeof arg == 'object'
+    return arg && typeof arg === 'object'
         && !arg.nodeName && !(Array.isArray(arg));
 };

+var processOptionsHooks = [];
+Dive.processOptionsHook = function(hook) {
+    if (typeof hook !== 'function') throw new Error(
+        'Dive.processOptionsHook expects a hook function as argument');
+    processOptionsHooks.push(hook);
+};
+
 var processOptions = function(el, options, content) {
+    for (var h = 0; h < processOptionsHooks.length; h++)
+        processOptionsHooks[h](el, options, content);
+
     if (options.hasOwnProperty('when') && !options.when) return null;
     if (options.prependContent) content.unshift(options.prependContent);
     if (options.appendContent) content.push(options.appendContent);
@@ -133,6 +147,9 @@ Dive.map = function(f, coll) {
     if (coll == undefined)
         return function(coll2) { return Dive.map(f, coll2); };

+    var caller = arguments.callee.caller,
+        ctx = caller.$dive_ctx;
+
     var results = [];
     for (var i = 0; i < coll.length; i++) {
         f.$index = i;
@@ -201,7 +218,7 @@ Dive.pipe = function(val) {
     var result = val;
     for (var i = 1; i < arguments.length; i++) {
         var fn = arguments[i];
-        if (typeof fn != "function")
+        if (typeof fn !== "function")
             throw new Error("Dive: pipe argument " + i + "is not a function");
         else result = fn(result);
     }
@@ -244,4 +261,91 @@ Dive.bindOptions = function() {
     };
 };

+Dive.cell = function(initialValue) {
+    var cell = initialValue,
+        depFor = [],
+        fn = function(setValue) {
+            if (setValue == undefined) {
+                var caller = arguments.callee.caller,
+                    ctx = caller.$dive_ctx;
+
+                if (ctx) {
+                    var alreadyRegistered = false;
+                    for (var k = 0; k < depFor.length; k++)
+                        if (ctx.id === depFor[k].$dive_ctx.id)
+                            alreadyRegistered = true;
+                    if (!alreadyRegistered) {
+                        //ctx.deps.push(fn);
+                        depFor.push(caller);
+                    }
+                }
+
+                return cell;
+            } else {
+                cell = setValue;
+                for (var i = 0; i < depFor.length; i++) {
+                    var depfor = depFor[i],
+                        dctx = depfor.$dive_ctx;
+
+                    if (dctx)
+                        switch (dctx.type) {
+                        case 'lazy':
+                            dctx.dirty = true;
+                            break;
+                        default:
+                            //depfor.update.call(depfor.update, true);
+                            break;
+                        }
+                }
+                return setValue;
+            }
+        };
+    return fn;
+};
+
+var idInc = 0,
+    lastStamp,
+    genId = function() {
+        var stamp = new Date().getTime().toString();
+        if (stamp === lastStamp) idInc++;
+        else idInc = 0;
+        lastStamp = stamp;
+
+        var inc = idInc.toString(),
+            n = stamp.length - inc.length;
+
+        if (inc < 0) throw new Error("You've overflown Dive's id generation.");
+        return stamp + Dive.repeat(n, '0') + inc;
+    };
+
+var generateContext = function(type) {
+    return {
+        id: genId(),
+        type: type,
+        dirty: false,
+        deps: []
+    };
+};
+
+Dive.cellf = function(context, type) {
+    if (typeof context !== 'function') throw new Error(
+        'Dive.cellf expected a function as sole argument');
+
+    context.$dive_ctx = generateContext(type);
+    var computed = context.call(context);
+
+    return function(debug) {
+        if (context.$dive_ctx.dirty) {
+            //if (debug) console.info('hey', hey);
+            context.$dive_ctx.dirty = false;
+            computed = context.call(context);
+        }
+        return computed;
+    };
+};
+
+for (var p = 0; p < plugins.length; p++) {
+    if (typeof plugins[p] === 'function') plugins[p]();
+}
+
 })();
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index 07c0cc8..a59eade 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -168,4 +168,39 @@ describe('Dive', function () {
         divefn3 = Dive.bindOptions({ appendContent: 'd' }, divefn3);
         expect(divefn3('orl').outerHTML).toEqual('<div id="hello">world</div>');
     });
+
+    it("updates and retrieves a cell's value", function() {
+        var cell = Dive.cell(3);
+        expect(cell()).toEqual(3);
+        cell(5);
+        expect(cell()).toEqual(5);
+    });
+
+    it("registers lazy cell deps. in a cell function's context", function() {
+        var cell1 = Dive.cell('a');
+        expect(cell1()).toEqual('a');
+        var cell1L = Dive.cellf(function() { return cell1() + 'b'; },
+                              'lazy');
+
+        expect(cell1L()).toEqual('ab');
+        cell1('b'); expect(cell1L()).toEqual('bb');
+        cell1('a'); expect(cell1L()).toEqual('ab');
+
+        var spy = jasmine.createSpy(),
+            cell2 = Dive.cell(1),
+            cell2L = Dive.cellf(function() {
+                spy();
+                return cell1() + cell2();
+            }, 'lazy');
+
+        expect(spy.callCount).toEqual(1);
+        cell1('b'); cell2(4); cell1('c');
+        expect(spy.callCount).toEqual(1);
+        expect(cell2L()).toEqual('c4');
+        cell2L();
+        expect(spy.callCount).toEqual(2);
+
+        cell1('z'); cell2(9);
+        expect(cell2L()).toEqual('z9');
+    });
 });

commit d1031475d4bc8573363df257732a78bdd9e4c52a
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Fri Apr 11 15:44:29 2014 -0300

    implement Dive.repeat templating function

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 4c151f6..d714938 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -180,6 +180,22 @@ Dive.takeWhile = function(f, coll) {
     return coll.slice(0, n);
 };

+Dive.repeat = function(n, val) {
+    if (val == undefined)
+        return function(val2) { return Dive.repeat(n, val2); };
+
+    var getVal;
+    // if val is an html node, we need to clone it each time,
+    // otherwise it will be just one object on the DOM.
+    if (val.nodeName) getVal = function() { return val.cloneNode(true); };
+    else getVal = function() { return val; };
+
+    var arr = new Array(n);
+    for (var i = 0; i < n; i++)
+        arr[i] = getVal();
+    return arr;
+};
+
 Dive.pipe = function(val) {
     if (arguments.length <= 1) return val;
     var result = val;
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index c392cf4..07c0cc8 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -81,6 +81,11 @@ describe('Dive', function () {
         expect(Dive.take(undefined, [1, 2])).toEqual([1, 2]);
     });

+    it("repeats a value n times", function() {
+        expect(Dive.div(Dive.repeat(3, Dive.b('a'))).outerHTML)
+            .toEqual('<div><b>a</b><b>a</b><b>a</b></div>');
+    });
+
     it("pipe values through transformations", function() {
         expect(Dive.div(
             Dive.pipe([0, 'a', 1, 'b', null],

commit 71639b81ff207fa225bbaae58c4ac397abdcc620
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Fri Apr 11 08:26:51 2014 -0300

    add Dive() selector-syntax form and other options

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index b30b78a..4c151f6 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -1,9 +1,57 @@
 (function() {
-window.Dive || (window.Dive = {});
 var elements = ['div', 'span', 'a', 'em', 'i', 'b', 'strong',
-                'table', 'thead', 'tbody', 'tr', 'td', 'th'];
+                'table', 'thead', 'tbody', 'tr', 'td', 'th',
+                'input', 'label', 'select', 'option', 'form'],
+    selector_syntax_error = new Error(
+        "Dive: expected selector-syntax string as first argument to Dive()"),
+    plugins;
+
+if (Dive) plugins = Dive;
+
+window.Dive = function() {
+    var type, first = arguments[0], args = [];
+    if (typeof first !== 'string') throw selector_syntax_error;
+
+    var idx = first.search(/[\.#]/);
+    if (idx == -1) type = first;
+    else if (idx == 0) type = 'div';
+    else {
+        type = first.substr(0, idx);
+        first = first.substr(idx);
+    }
+
+    if (elements.indexOf(type) != -1) {
+        for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
+        return createElement(type, args, { css: first });
+    }
+    else throw selector_syntax_error;
+};

 Dive.identity = function(i) { return i; };
+Dive.into = function(dest_, orig_, mergeStrategy) {
+    var opt,
+        dest = dest_ || {},
+        orig = orig_ || {};
+
+    switch (mergeStrategy) {
+    case 'add':
+        for (opt in orig)
+            if (orig.hasOwnProperty(opt)) {
+                if (dest.hasOwnProperty(opt))
+                    dest[opt] += orig[opt];
+                else dest[opt] = orig[opt];
+            }
+        break;
+    default:
+        for (opt in orig)
+            if (orig.hasOwnProperty(opt))
+                dest[opt] = orig[opt];
+        break;
+    }
+    return dest;
+};
+
+Dive.into(Dive, plugins);

 var appendContent = function(parent, content) {
     if (content == undefined || content == null || typeof content == "boolean")
@@ -27,6 +75,18 @@ var processOptions = function(el, options, content) {
     for (var i = 0; i < content.length; i++)
         appendContent(el, content[i]);

+    if (options.attr)
+        for (var attr in options.attr)
+            if (options.attr.hasOwnProperty(attr))
+                el.setAttribute(attr, options.attr[attr]);
+
+    if (options.data)
+        for (var datum in options.data)
+            if (options.data.hasOwnProperty(datum))
+                el.setAttribute('data-' + datum, options.data[datum]);
+
+    if (options.href) el.setAttribute('href', options.href);
+    if (options.value) el.setAttribute('value', options.value);
     if (options.css) {
         var cssParser = new Holmes.CssConfigParser(options.css),
             classes = cssParser.classList(),
@@ -38,15 +98,23 @@ var processOptions = function(el, options, content) {
     return el;
 };

-var createElement = function(type, args) {
+var createElement = function(type, args, extraOptions) {
     var el = document.createElement(type);
-    if (args.length) {
-        var first = args[0];
+    if (args.length || extraOptions) {
+        var first = args[0],
+            start = 0,
+            options;

         if (gotOptions(first)) {
+            options = first;
+            start = 1;
+        }
+
+        if (extraOptions) options = Dive.into(options, extraOptions, 'add');
+        if (options) {
             var rest = [];
-            for (var i = 1; i < args.length; i++) rest.push(args[i]);
-            el = processOptions(el, first, rest);
+            for (var i = start; i < args.length; i++) rest.push(args[i]);
+            el = processOptions(el, options, rest);
             if (!el) return null;
         } else {
             for (var j = 0; j < args.length; j++) appendContent(el, args[j]);
@@ -149,14 +217,7 @@ Dive.bindOptions = function() {

             if (gotOptions(first)) {
                 start = 1;
-
-                switch (mergeStrategy) {
-                    case 'replace':
-                    for (var opt in first)
-                        if (first.hasOwnProperty(opt))
-                            finalOptions[opt] = first[opt];
-                    break;
-                }
+                Dive.into(finalOptions, first, mergeStrategy);
             }

             for (var i = start; i < arguments.length; i++)
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index ed51803..c392cf4 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -14,7 +14,8 @@ describe('Dive', function () {

         expect(Dive.b({ when: hellFreezesOver }, "a")).toEqual(null);
         expect(Dive.b({ when: undefined }, "a")).toEqual(null);
-        expect(Dive.b({ when: theSkyIsBlue }, "a").outerHTML).toEqual("<b>a</b>");
+        expect(Dive.b({ when: theSkyIsBlue }, "a").outerHTML)
+            .toEqual("<b>a</b>");
     });

     it("parses a single options argument correctly", function() {
@@ -24,6 +25,13 @@ describe('Dive', function () {
             .toEqual('<b class="hey" id="you"></b>');
     });

+    it("accepts selector-syntax argument to Dive() function", function() {
+        expect(Dive('.to.be').outerHTML).toEqual('<div class="to be"></div>');
+        expect(Dive('b.to.be').outerHTML).toEqual('<b class="to be"></b>');
+        expect(Dive('b.to#be', { css: '.hey#you' }).outerHTML)
+            .toEqual('<b class="hey to" id="you be"></b>');
+    });
+
     it("weeds out null elements", function() {
         expect(Dive.div(
             Dive.span({ when: hellFreezesOver }, "a")
@@ -132,6 +140,16 @@ describe('Dive', function () {
                .outerHTML).toEqual('<b><div></div>a<span></span></b>');
     });

+    it("sets attributes on an element through the 'attr' option", function() {
+        expect(Dive.b({ attr: { value: 1 } }).outerHTML)
+            .toEqual('<b value="1"></b>');
+        var el = Dive.b({ attr: { value: '1', class: 'hey you' },
+                          data: { hey: 2 } });
+        expect(el.className).toEqual('hey you');
+        expect(el.getAttribute('value')).toEqual('1');
+        expect(el.getAttribute('data-hey')).toEqual('2');
+    });
+
     it("binds partial options to a Dive.div function", function() {
         var divefn = Dive.bindOptions({ css: '.hey' }, Dive.div);
         expect(divefn().outerHTML).toEqual('<div class="hey"></div>');

commit db8fa85e7798105d90b29c0ff492e4eb7e2bc330
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Mar 19 16:04:22 2014 -0300

    change Dive.bindOptions' argument order

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 8f5f4ea..b30b78a 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -124,10 +124,23 @@ Dive.pipe = function(val) {
     return result;
 };

-Dive.bindOptions = function(divefn, options, mergeStrategy) {
+Dive.bindOptions = function() {
+    var options, divefn, mergeStrategy;
+    switch (arguments.length) {
+        case 2:
+        options = arguments[0];
+        mergeStrategy = 'replace';
+        divefn = arguments[1];
+        break;
+        case 3:
+        options = arguments[0];
+        mergeStrategy = arguments[1];
+        divefn = arguments[2];
+        break;
+    }
+
     return function() {
         var finalOptions = options,
-            strategy = mergeStrategy || 'replace',
             args = [];

         if (arguments.length) {
@@ -137,7 +150,7 @@ Dive.bindOptions = function(divefn, options, mergeStrategy) {
             if (gotOptions(first)) {
                 start = 1;

-                switch (strategy) {
+                switch (mergeStrategy) {
                     case 'replace':
                     for (var opt in first)
                         if (first.hasOwnProperty(opt))
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index 7bdd08b..ed51803 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -133,16 +133,16 @@ describe('Dive', function () {
     });

     it("binds partial options to a Dive.div function", function() {
-        var divefn = Dive.bindOptions(Dive.div, { css: '.hey' });
+        var divefn = Dive.bindOptions({ css: '.hey' }, Dive.div);
         expect(divefn().outerHTML).toEqual('<div class="hey"></div>');

-        var divefn2 = Dive.bindOptions(Dive.div, { prependContent: 'he' });
+        var divefn2 = Dive.bindOptions({ prependContent: 'he' }, Dive.div);
         expect(divefn2('y').outerHTML).toEqual('<div>hey</div>');

-        var divefn3 = Dive.bindOptions(Dive.div, { css: '.hello' });
-        divefn3 = Dive.bindOptions(divefn3, { css: '#hello' });
-        divefn3 = Dive.bindOptions(divefn3, { prependContent: 'w' });
-        divefn3 = Dive.bindOptions(divefn3, { appendContent: 'd' });
+        var divefn3 = Dive.bindOptions({ css: '.hello' }, Dive.div);
+        divefn3 = Dive.bindOptions({ css: '#hello' }, divefn3);
+        divefn3 = Dive.bindOptions({ prependContent: 'w' }, divefn3);
+        divefn3 = Dive.bindOptions({ appendContent: 'd' }, divefn3);
         expect(divefn3('orl').outerHTML).toEqual('<div id="hello">world</div>');
     });
 });

commit be32f02320afc40a7da2f16282c96e61e89b6162
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Mar 19 14:11:28 2014 -0300

    implement appendContent & prependContent Dive opts

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index bc47208..8f5f4ea 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -20,8 +20,13 @@ var gotOptions = function(arg) {
         && !arg.nodeName && !(Array.isArray(arg));
 };

-var processOptions = function(el, options) {
+var processOptions = function(el, options, content) {
     if (options.hasOwnProperty('when') && !options.when) return null;
+    if (options.prependContent) content.unshift(options.prependContent);
+    if (options.appendContent) content.push(options.appendContent);
+    for (var i = 0; i < content.length; i++)
+        appendContent(el, content[i]);
+
     if (options.css) {
         var cssParser = new Holmes.CssConfigParser(options.css),
             classes = cssParser.classList(),
@@ -36,17 +41,15 @@ var processOptions = function(el, options) {
 var createElement = function(type, args) {
     var el = document.createElement(type);
     if (args.length) {
-        var first = args[0],
-            start = 0;
+        var first = args[0];

         if (gotOptions(first)) {
-            start = 1;
-            el = processOptions(el, first);
+            var rest = [];
+            for (var i = 1; i < args.length; i++) rest.push(args[i]);
+            el = processOptions(el, first, rest);
             if (!el) return null;
-        }
-
-        for (var i = start; i < args.length; i++) {
-            appendContent(el, args[i]);
+        } else {
+            for (var j = 0; j < args.length; j++) appendContent(el, args[j]);
         }
     }

diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index 5e7acf8..7bdd08b 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -113,8 +113,36 @@ describe('Dive', function () {
                      '</tbody></table>');
     });

+    it("prepends and appends content through optional settings", function() {
+        expect(Dive.b({ prependContent: '<div></div>' }).outerHTML)
+            .toEqual('<b><div></div></b>');
+        expect(Dive.b({ appendContent: '<div></div>' }).outerHTML)
+            .toEqual('<b><div></div></b>');
+        expect(Dive.b({ prependContent: '<div></div>',
+                        appendContent: '<div class="hey"></div>',
+                        css: '.hey' }).outerHTML)
+            .toEqual('<b class="hey"><div></div><div class="hey"></div></b>');
+
+        expect(Dive.b({ prependContent: '<div></div>' }, "a").outerHTML)
+            .toEqual('<b><div></div>a</b>');
+        expect(Dive.b({ appendContent: '<div></div>' }, "a").outerHTML)
+            .toEqual('<b>a<div></div></b>');
+        expect(Dive.b({ prependContent: '<div></div>',
+                        appendContent: '<span></span>' }, "a")
+               .outerHTML).toEqual('<b><div></div>a<span></span></b>');
+    });
+
     it("binds partial options to a Dive.div function", function() {
         var divefn = Dive.bindOptions(Dive.div, { css: '.hey' });
         expect(divefn().outerHTML).toEqual('<div class="hey"></div>');
+
+        var divefn2 = Dive.bindOptions(Dive.div, { prependContent: 'he' });
+        expect(divefn2('y').outerHTML).toEqual('<div>hey</div>');
+
+        var divefn3 = Dive.bindOptions(Dive.div, { css: '.hello' });
+        divefn3 = Dive.bindOptions(divefn3, { css: '#hello' });
+        divefn3 = Dive.bindOptions(divefn3, { prependContent: 'w' });
+        divefn3 = Dive.bindOptions(divefn3, { appendContent: 'd' });
+        expect(divefn3('orl').outerHTML).toEqual('<div id="hello">world</div>');
     });
 });

commit a7d8d844149e6c65567c3d9b33b2f5e413bd88da
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Mar 19 11:19:39 2014 -0300

    implement Dive.bindOptions for partialling divefns

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index ff81624..bc47208 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -121,4 +121,34 @@ Dive.pipe = function(val) {
     return result;
 };

+Dive.bindOptions = function(divefn, options, mergeStrategy) {
+    return function() {
+        var finalOptions = options,
+            strategy = mergeStrategy || 'replace',
+            args = [];
+
+        if (arguments.length) {
+            var first = arguments[0],
+                start = 0;
+
+            if (gotOptions(first)) {
+                start = 1;
+
+                switch (strategy) {
+                    case 'replace':
+                    for (var opt in first)
+                        if (first.hasOwnProperty(opt))
+                            finalOptions[opt] = first[opt];
+                    break;
+                }
+            }
+
+            for (var i = start; i < arguments.length; i++)
+                args.push(arguments[i]);
+        }
+
+        return divefn.apply(undefined, [finalOptions].concat(args));
+    };
+};
+
 })();
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index 9f5a87b..5e7acf8 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -112,4 +112,9 @@ describe('Dive', function () {
                      '<tr><td>1</td><td>b</td></tr>' +
                      '</tbody></table>');
     });
+
+    it("binds partial options to a Dive.div function", function() {
+        var divefn = Dive.bindOptions(Dive.div, { css: '.hey' });
+        expect(divefn().outerHTML).toEqual('<div class="hey"></div>');
+    });
 });

commit 3e154c9e5b9f5a3b11d26cdf015b4893a8cb0e6d
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Wed Mar 19 11:08:22 2014 -0300

    refactor dive opts processing & fix single arg bug

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 67ca812..ff81624 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -20,6 +20,19 @@ var gotOptions = function(arg) {
         && !arg.nodeName && !(Array.isArray(arg));
 };

+var processOptions = function(el, options) {
+    if (options.hasOwnProperty('when') && !options.when) return null;
+    if (options.css) {
+        var cssParser = new Holmes.CssConfigParser(options.css),
+            classes = cssParser.classList(),
+            ids = cssParser.idList();
+
+        if (classes) el.className = classes;
+        if (ids) el.setAttribute('id', ids);
+    }
+    return el;
+};
+
 var createElement = function(type, args) {
     var el = document.createElement(type);
     if (args.length) {
@@ -27,27 +40,16 @@ var createElement = function(type, args) {
             start = 0;

         if (gotOptions(first)) {
-            if (first.hasOwnProperty('when') && !first.when) return null;
-            if (first.css) {
-                var cssParser = new Holmes.CssConfigParser(first.css),
-                    classes = cssParser.classList(),
-                    ids = cssParser.idList();
-
-                if (classes) el.className = classes;
-                if (ids) el.setAttribute('id', ids);
-            }
-
             start = 1;
+            el = processOptions(el, first);
+            if (!el) return null;
         }

-        if (args.length == 1) {
-            appendContent(el, first);
-        } else {
-            for (var i = start; i < args.length; i++) {
-                appendContent(el, args[i]);
-            }
+        for (var i = start; i < args.length; i++) {
+            appendContent(el, args[i]);
         }
     }
+
     return el;
 };

@@ -113,7 +115,7 @@ Dive.pipe = function(val) {
     for (var i = 1; i < arguments.length; i++) {
         var fn = arguments[i];
         if (typeof fn != "function")
-            throw new Error("Dive: thread argument " + i + "is not a function");
+            throw new Error("Dive: pipe argument " + i + "is not a function");
         else result = fn(result);
     }
     return result;
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index d8d0cf3..9f5a87b 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -12,13 +12,18 @@ describe('Dive', function () {
         expect(Dive.b("a").outerHTML).toEqual("<b>a</b>");
         expect(Dive.b({}, "a").outerHTML).toEqual("<b>a</b>");

-        expect(Dive.b({ when: hellFreezesOver})).toEqual(null);
         expect(Dive.b({ when: hellFreezesOver }, "a")).toEqual(null);
-        expect(Dive.b({ when: undefined })).toEqual(null);
         expect(Dive.b({ when: undefined }, "a")).toEqual(null);
         expect(Dive.b({ when: theSkyIsBlue }, "a").outerHTML).toEqual("<b>a</b>");
     });

+    it("parses a single options argument correctly", function() {
+        expect(Dive.b({ when: hellFreezesOver})).toEqual(null);
+        expect(Dive.b({ when: undefined })).toEqual(null);
+        expect(Dive.b({ css: '.hey#you'}).outerHTML)
+            .toEqual('<b class="hey" id="you"></b>');
+    });
+
     it("weeds out null elements", function() {
         expect(Dive.div(
             Dive.span({ when: hellFreezesOver }, "a")

commit 78ab0669f8c7fc2597d0e7c5e67a8f570823cb9c
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Fri Mar 14 18:20:38 2014 -0300

    treat Dive.div({ when: test }) single arg case

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 4f8904e..67ca812 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -2,7 +2,7 @@
 window.Dive || (window.Dive = {});
 var elements = ['div', 'span', 'a', 'em', 'i', 'b', 'strong',
                 'table', 'thead', 'tbody', 'tr', 'td', 'th'];
-
+
 Dive.identity = function(i) { return i; };

 var appendContent = function(parent, content) {
@@ -21,14 +21,11 @@ var gotOptions = function(arg) {
 };

 var createElement = function(type, args) {
-    var el = document.createElement(type),
-        first = args[0];
-    switch (args.length) {
-        case 0: break;
-        case 1: appendContent(el, first);
-                break;
-        default:
-        var start = 0;
+    var el = document.createElement(type);
+    if (args.length) {
+        var first = args[0],
+            start = 0;
+
         if (gotOptions(first)) {
             if (first.hasOwnProperty('when') && !first.when) return null;
             if (first.css) {
@@ -43,13 +40,17 @@ var createElement = function(type, args) {
             start = 1;
         }

-        for (var i = start; i < args.length; i++) {
-            appendContent(el, args[i]);
+        if (args.length == 1) {
+            appendContent(el, first);
+        } else {
+            for (var i = start; i < args.length; i++) {
+                appendContent(el, args[i]);
+            }
         }
     }
     return el;
 };
-
+
 for (var i = 0; i < elements.length; i++)
     (function(type) {
         Dive[type] = function() { return createElement(type, arguments); };
@@ -67,7 +68,7 @@ Dive.map = function(f, coll) {
     delete f.$index;
     return results;
 };
-
+
 Dive.filter = function(f, coll) {
     if (coll == undefined)
         return function(coll2) { return Dive.filter(f, coll2); };
@@ -79,7 +80,7 @@ Dive.filter = function(f, coll) {
     }
     return results;
 };
-
+
 Dive.mapSome = function(f, coll) {
     if (coll == undefined)
         return function(coll2) { return Dive.mapSome(f, coll2); };
@@ -88,7 +89,7 @@ Dive.mapSome = function(f, coll) {
         return c != null && c != undefined;
     }, coll));
 };
-
+
 Dive.take = function(n, coll) {
     if (coll == undefined)
         if (n == undefined || n == null) return Dive.identity;
@@ -105,7 +106,7 @@ Dive.takeWhile = function(f, coll) {
     while (f(coll[i])) n++;
     return coll.slice(0, n);
 };
-
+
 Dive.pipe = function(val) {
     if (arguments.length <= 1) return val;
     var result = val;
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index be70b02..d8d0cf3 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -11,11 +11,14 @@ describe('Dive', function () {
         expect(Dive.b(0).outerHTML).toEqual("<b>0</b>");
         expect(Dive.b("a").outerHTML).toEqual("<b>a</b>");
         expect(Dive.b({}, "a").outerHTML).toEqual("<b>a</b>");
+
+        expect(Dive.b({ when: hellFreezesOver})).toEqual(null);
         expect(Dive.b({ when: hellFreezesOver }, "a")).toEqual(null);
+        expect(Dive.b({ when: undefined })).toEqual(null);
         expect(Dive.b({ when: undefined }, "a")).toEqual(null);
         expect(Dive.b({ when: theSkyIsBlue }, "a").outerHTML).toEqual("<b>a</b>");
     });
-
+
     it("weeds out null elements", function() {
         expect(Dive.div(
             Dive.span({ when: hellFreezesOver }, "a")
@@ -35,20 +38,20 @@ describe('Dive', function () {
             Dive.b("c")
         ).outerHTML).toEqual("<div><b>a</b><i>1</i><i>2</i><i>3</i><b>c</b></div>");
     });
-
+
     it("maps values through functions and expands dom nodes in-place", function() {
         expect(Dive.div(
             Dive.map(function(k) { return Dive.b(k); }, ['c', 'd']),
             Dive.map(function(k) { return Dive.b(k); }, ['d', 'e'])
         ).outerHTML).toEqual("<div><b>c</b><b>d</b><b>d</b><b>e</b></div>");
-
+
         expect(Dive.div(
             Dive.mapSome(Dive.b, [0, 1, null, 2])
         ).outerHTML).toEqual("<div><b>0</b><b>1</b><b>2</b></div>");
-
+
         expect(Dive.span(Dive.map(Dive.b, [1, 2]))
                .outerHTML).toEqual("<span><b>1</b><b>2</b></span>");
-
+
         var mapToI = Dive.map(function(i) { return Dive.b({ when: i }, i); });
         expect(Dive.div(
             mapToI([1, null, 2])
@@ -74,4 +77,34 @@ describe('Dive', function () {
                       Dive.map(function(c) { return Dive.span('a' + c); })
         )).outerHTML).toEqual("<div><span>aaa</span><span>a2</span></div>");
     });
+
+    it("renders a table", function() {
+        var table = Dive.table(
+            { css: '.hey#you' },
+            Dive.thead(
+                Dive.tr({ when: true },
+                        Dive.th({ when: undefined }),
+                        Dive.map(function(c) {
+                            return Dive.th(c);
+                        }, [1, 2])
+                       )),
+            Dive.tbody(
+                Dive.pipe(['a', 'b', 'c'],
+                          Dive.take(2),
+                          Dive.map(function(row) {
+                              return Dive.tr(
+                                  Dive.td(this.$index),
+                                  Dive.td(row)
+                              );
+                          })))
+        );
+
+        expect(table.outerHTML)
+            .toEqual('<table class="hey" id="you">' +
+                     '<thead><tr><th>1</th><th>2</th></tr></thead>' +
+                     '<tbody>' +
+                     '<tr><td>0</td><td>a</td></tr>' +
+                     '<tr><td>1</td><td>b</td></tr>' +
+                     '</tbody></table>');
+    });
 });

commit d66f91738d111b1e70608d09aae2cea831dd5f22
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Fri Mar 7 16:25:01 2014 -0300

    add Dive css attributes only if non-empty

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index 476f7e6..4f8904e 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -32,9 +32,12 @@ var createElement = function(type, args) {
         if (gotOptions(first)) {
             if (first.hasOwnProperty('when') && !first.when) return null;
             if (first.css) {
-                var cssParser = new Holmes.CssConfigParser(first.css);
-                el.className = cssParser.classList();
-                el.setAttribute('id', cssParser.idList());
+                var cssParser = new Holmes.CssConfigParser(first.css),
+                    classes = cssParser.classList(),
+                    ids = cssParser.idList();
+
+                if (classes) el.className = classes;
+                if (ids) el.setAttribute('id', ids);
             }

             start = 1;

commit fa7e61857b6da4296c9b128f0e419658566fec13
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Fri Mar 7 13:23:08 2014 -0300

    improve Dive's API consistency and specs

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
index b8396f0..476f7e6 100644
--- a/src/main/webapp/javascript/common/dive.js
+++ b/src/main/webapp/javascript/common/dive.js
@@ -1,12 +1,13 @@
 (function() {
 window.Dive || (window.Dive = {});
 var elements = ['div', 'span', 'a', 'em', 'i', 'b', 'strong',
-                'table', 'tr', 'td', 'th'];
+                'table', 'thead', 'tbody', 'tr', 'td', 'th'];

 Dive.identity = function(i) { return i; };

 var appendContent = function(parent, content) {
-    if (!content) return;
+    if (content == undefined || content == null || typeof content == "boolean")
+        return;
     else if (content.nodeName) parent.appendChild(content);
     else if (content instanceof Array)
         for (var i = 0; i < content.length; i++)
@@ -14,6 +15,11 @@ var appendContent = function(parent, content) {
     else parent.innerHTML += content;
 };

+var gotOptions = function(arg) {
+    return arg && typeof arg == 'object'
+        && !arg.nodeName && !(Array.isArray(arg));
+};
+
 var createElement = function(type, args) {
     var el = document.createElement(type),
         first = args[0];
@@ -23,7 +29,7 @@ var createElement = function(type, args) {
                 break;
         default:
         var start = 0;
-        if (typeof first == 'object' && !first.nodeName && !(first instanceof Array)) {
+        if (gotOptions(first)) {
             if (first.hasOwnProperty('when') && !first.when) return null;
             if (first.css) {
                 var cssParser = new Holmes.CssConfigParser(first.css);
@@ -52,9 +58,10 @@ Dive.map = function(f, coll) {

     var results = [];
     for (var i = 0; i < coll.length; i++) {
-        var mapped = f(coll[i]);
-        if (mapped) results.push(mapped);
+        f.$index = i;
+        results.push(f.call(f, coll[i]));
     }
+    delete f.$index;
     return results;
 };

@@ -63,8 +70,10 @@ Dive.filter = function(f, coll) {
         return function(coll2) { return Dive.filter(f, coll2); };

     var results = [];
-    for (var i = 0; i < coll.length; i++)
+    for (var i = 0; i < coll.length; i++) {
+        f.$index = i;
         if (f(coll[i])) results.push(coll[i]);
+    }
     return results;
 };

@@ -79,8 +88,11 @@ Dive.mapSome = function(f, coll) {

 Dive.take = function(n, coll) {
     if (coll == undefined)
-        return function(coll2) { return Dive.take(n, coll2); };
-    return coll.slice(0, n - 1);
+        if (n == undefined || n == null) return Dive.identity;
+        else return function(coll2) { return Dive.take(n, coll2); };
+
+    if (n == undefined || n == null) return coll;
+    else return coll.slice(0, n);
 };

 Dive.takeWhile = function(f, coll) {
@@ -91,7 +103,7 @@ Dive.takeWhile = function(f, coll) {
     return coll.slice(0, n);
 };

-Dive.thread = function(val) {
+Dive.pipe = function(val) {
     if (arguments.length <= 1) return val;
     var result = val;
     for (var i = 1; i < arguments.length; i++) {
diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
index 9e5ba8d..be70b02 100644
--- a/src/main/webapp/spec/common/DiveSpec.js
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -4,8 +4,11 @@ describe('Dive', function () {

     it("renders simple elements", function() {
         expect(Dive.b().outerHTML).toEqual("<b></b>");
-        expect(Dive.b(null).outerHTML).toEqual("<b></b>");
-        expect(Dive.b(undefined).outerHTML).toEqual("<b></b>");
+        _.each([null, undefined, false, true], function(content) {
+            expect(Dive.b(content).outerHTML).toEqual("<b></b>");
+            expect(Dive.b({}, content).outerHTML).toEqual("<b></b>");
+        });
+        expect(Dive.b(0).outerHTML).toEqual("<b>0</b>");
         expect(Dive.b("a").outerHTML).toEqual("<b>a</b>");
         expect(Dive.b({}, "a").outerHTML).toEqual("<b>a</b>");
         expect(Dive.b({ when: hellFreezesOver }, "a")).toEqual(null);
@@ -17,6 +20,13 @@ describe('Dive', function () {
         expect(Dive.div(
             Dive.span({ when: hellFreezesOver }, "a")
         ).outerHTML).toEqual("<div></div>");
+
+        expect(Dive.div(
+            Dive.span({ when: hellFreezesOver }, "a"),
+            Dive.span("b"),
+            Dive.span({ when: hellFreezesOver }, "c")
+        ).outerHTML).toEqual("<div><span>b</span></div>");
+
         expect(Dive.div(
             Dive.b("a"),
             Dive.map(function(i) {
@@ -33,27 +43,35 @@ describe('Dive', function () {
         ).outerHTML).toEqual("<div><b>c</b><b>d</b><b>d</b><b>e</b></div>");

         expect(Dive.div(
-            Dive.mapSome(Dive.b, [1, null, 2])
-        ).outerHTML).toEqual("<div><b>1</b><b>2</b></div>");
+            Dive.mapSome(Dive.b, [0, 1, null, 2])
+        ).outerHTML).toEqual("<div><b>0</b><b>1</b><b>2</b></div>");

         expect(Dive.span(Dive.map(Dive.b, [1, 2]))
                .outerHTML).toEqual("<span><b>1</b><b>2</b></span>");
-    });
-
-    it("partially applies a map (or filter, take) if given one argument", function() {
+
         var mapToI = Dive.map(function(i) { return Dive.b({ when: i }, i); });
         expect(Dive.div(
             mapToI([1, null, 2])
         ).outerHTML).toEqual("<div><b>1</b><b>2</b></div>");
     });
-
-    it("threads values through transformations", function() {
+
+    it("takes n elements from a collection", function() {
+        expect(Dive.take(3, [null, undefined, 0, 1])).toEqual([null, undefined, 0]);
+
+        var take2 = Dive.take(2);
+        expect(take2([1, 2, 3, 4])).toEqual([1, 2]);
+
+        expect(Dive.take(null, [1, 2])).toEqual([1, 2]);
+        expect(Dive.take(undefined, [1, 2])).toEqual([1, 2]);
+    });
+
+    it("pipe values through transformations", function() {
         expect(Dive.div(
-            Dive.thread([0, 'a', 1, 'b', null],
-                        Dive.mapSome(function(c) { return c + c; }),
-                        Dive.take(3),
-                        Dive.filter(Dive.identity),
-                        Dive.map(function(c) { return Dive.span('a' + c); })
+            Dive.pipe([0, 'a', 1, 'b', null],
+                      Dive.mapSome(function(c) { return c + c; }),
+                      Dive.take(3),
+                      Dive.filter(Dive.identity),
+                      Dive.map(function(c) { return Dive.span('a' + c); })
         )).outerHTML).toEqual("<div><span>aaa</span><span>a2</span></div>");
     });
 });

commit 165a66f23f81ccb65b42c509f4b393fb4d5063bb
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Tue Feb 25 20:11:17 2014 -0300

    test initial Dive functionality

diff --git a/src/main/webapp/spec/common/DiveSpec.js b/src/main/webapp/spec/common/DiveSpec.js
new file mode 100644
index 0000000..9e5ba8d
--- /dev/null
+++ b/src/main/webapp/spec/common/DiveSpec.js
@@ -0,0 +1,59 @@
+describe('Dive', function () {
+    var theSkyIsBlue = true;
+    var hellFreezesOver = false;
+
+    it("renders simple elements", function() {
+        expect(Dive.b().outerHTML).toEqual("<b></b>");
+        expect(Dive.b(null).outerHTML).toEqual("<b></b>");
+        expect(Dive.b(undefined).outerHTML).toEqual("<b></b>");
+        expect(Dive.b("a").outerHTML).toEqual("<b>a</b>");
+        expect(Dive.b({}, "a").outerHTML).toEqual("<b>a</b>");
+        expect(Dive.b({ when: hellFreezesOver }, "a")).toEqual(null);
+        expect(Dive.b({ when: undefined }, "a")).toEqual(null);
+        expect(Dive.b({ when: theSkyIsBlue }, "a").outerHTML).toEqual("<b>a</b>");
+    });
+
+    it("weeds out null elements", function() {
+        expect(Dive.div(
+            Dive.span({ when: hellFreezesOver }, "a")
+        ).outerHTML).toEqual("<div></div>");
+        expect(Dive.div(
+            Dive.b("a"),
+            Dive.map(function(i) {
+                return (i && Dive.i(i)) || null;
+            }, [1, 2, undefined, 3, 0]),
+            Dive.b("c")
+        ).outerHTML).toEqual("<div><b>a</b><i>1</i><i>2</i><i>3</i><b>c</b></div>");
+    });
+
+    it("maps values through functions and expands dom nodes in-place", function() {
+        expect(Dive.div(
+            Dive.map(function(k) { return Dive.b(k); }, ['c', 'd']),
+            Dive.map(function(k) { return Dive.b(k); }, ['d', 'e'])
+        ).outerHTML).toEqual("<div><b>c</b><b>d</b><b>d</b><b>e</b></div>");
+
+        expect(Dive.div(
+            Dive.mapSome(Dive.b, [1, null, 2])
+        ).outerHTML).toEqual("<div><b>1</b><b>2</b></div>");
+
+        expect(Dive.span(Dive.map(Dive.b, [1, 2]))
+               .outerHTML).toEqual("<span><b>1</b><b>2</b></span>");
+    });
+
+    it("partially applies a map (or filter, take) if given one argument", function() {
+        var mapToI = Dive.map(function(i) { return Dive.b({ when: i }, i); });
+        expect(Dive.div(
+            mapToI([1, null, 2])
+        ).outerHTML).toEqual("<div><b>1</b><b>2</b></div>");
+    });
+
+    it("threads values through transformations", function() {
+        expect(Dive.div(
+            Dive.thread([0, 'a', 1, 'b', null],
+                        Dive.mapSome(function(c) { return c + c; }),
+                        Dive.take(3),
+                        Dive.filter(Dive.identity),
+                        Dive.map(function(c) { return Dive.span('a' + c); })
+        )).outerHTML).toEqual("<div><span>aaa</span><span>a2</span></div>");
+    });
+});

commit 400a59ef9f9d7e657d5452888ed888daa2e2b919
Author: Vic Goldfeld <vic@longstorm.org>
Date:   Tue Feb 25 20:10:42 2014 -0300

    create Dive templating functions

diff --git a/src/main/webapp/javascript/common/dive.js b/src/main/webapp/javascript/common/dive.js
new file mode 100644
index 0000000..b8396f0
--- /dev/null
+++ b/src/main/webapp/javascript/common/dive.js
@@ -0,0 +1,106 @@
+(function() {
+window.Dive || (window.Dive = {});
+var elements = ['div', 'span', 'a', 'em', 'i', 'b', 'strong',
+                'table', 'tr', 'td', 'th'];
+
+Dive.identity = function(i) { return i; };
+
+var appendContent = function(parent, content) {
+    if (!content) return;
+    else if (content.nodeName) parent.appendChild(content);
+    else if (content instanceof Array)
+        for (var i = 0; i < content.length; i++)
+            appendContent(parent, content[i]);
+    else parent.innerHTML += content;
+};
+
+var createElement = function(type, args) {
+    var el = document.createElement(type),
+        first = args[0];
+    switch (args.length) {
+        case 0: break;
+        case 1: appendContent(el, first);
+                break;
+        default:
+        var start = 0;
+        if (typeof first == 'object' && !first.nodeName && !(first instanceof Array)) {
+            if (first.hasOwnProperty('when') && !first.when) return null;
+            if (first.css) {
+                var cssParser = new Holmes.CssConfigParser(first.css);
+                el.className = cssParser.classList();
+                el.setAttribute('id', cssParser.idList());
+            }
+
+            start = 1;
+        }
+
+        for (var i = start; i < args.length; i++) {
+            appendContent(el, args[i]);
+        }
+    }
+    return el;
+};
+
+for (var i = 0; i < elements.length; i++)
+    (function(type) {
+        Dive[type] = function() { return createElement(type, arguments); };
+    })(elements[i]);
+
+Dive.map = function(f, coll) {
+    if (coll == undefined)
+        return function(coll2) { return Dive.map(f, coll2); };
+
+    var results = [];
+    for (var i = 0; i < coll.length; i++) {
+        var mapped = f(coll[i]);
+        if (mapped) results.push(mapped);
+    }
+    return results;
+};
+
+Dive.filter = function(f, coll) {
+    if (coll == undefined)
+        return function(coll2) { return Dive.filter(f, coll2); };
+
+    var results = [];
+    for (var i = 0; i < coll.length; i++)
+        if (f(coll[i])) results.push(coll[i]);
+    return results;
+};
+
+Dive.mapSome = function(f, coll) {
+    if (coll == undefined)
+        return function(coll2) { return Dive.mapSome(f, coll2); };
+
+    return Dive.map(f, Dive.filter(function(c) {
+        return c != null && c != undefined;
+    }, coll));
+};
+
+Dive.take = function(n, coll) {
+    if (coll == undefined)
+        return function(coll2) { return Dive.take(n, coll2); };
+    return coll.slice(0, n - 1);
+};
+
+Dive.takeWhile = function(f, coll) {
+    if (coll == undefined)
+        return function(coll2) { return Dive.takeWhile(f, coll2); };
+    var n = 0;
+    while (f(coll[i])) n++;
+    return coll.slice(0, n);
+};
+
+Dive.thread = function(val) {
+    if (arguments.length <= 1) return val;
+    var result = val;
+    for (var i = 1; i < arguments.length; i++) {
+        var fn = arguments[i];
+        if (typeof fn != "function")
+            throw new Error("Dive: thread argument " + i + "is not a function");
+        else result = fn(result);
+    }
+    return result;
+};
+
+})();