Voodookit
Voodookit: Test Suite
A Better <table>
HTML
<tr> <th>Item</th> <th>Quantity</th> </tr>
<tr> <td>Table</td> <td>1.0</td> </tr>
<tr> <td>Chair</td> <td>4.0</td> </tr>
</table>
Javascript
Demos and Tests
| Item | Quantity |
|---|---|
| Table | 1.0 |
| Chair | 4.0 |
Demo: vkex1.col(1).sort()
Demo: vkex1.col(1).sortReverse()
?Test: vkex1.numRows() → ? (expected 3)
?Test: vkex1.numCols() → ? (expected 2)
?Test: vkex1.numContentRows() → ? (expected 2)
?Test: vkex1.numHeaderRows() → ? (expected 1)
In addition to getting general information about the table, you can query for a specific cell by (row, col) position. Position coordinates are 0-indexed from the top left, and non-content rows are skipped. The return value is an instance of VkCell.
?Test: vkex1.cell(0,1).value() → ? (expected "1.0")
?Test: vkex1.cell(0,1).toString() → ? (expected "1.0")
?Test: vkex1.cell(1,0) → ? (expected "Chair")
If you want to handle an entire column at once, you can request it by index:
?Test: vkex1.col(1).cellValues().join(", ") → ? (expected "1.0, 4.0")
You can even calculate aggregate values for a column using the reduce function:
?Test: vkex1.col(1).reduce( function(x,y) { return parseFloat(x)+parseFloat(y); } ) → ? (expected 5.0)
Or find matching cells using the grep function, which returns an array of VkCells for which the test function returns a true value:
?Test: vkex1.col(0).grep( function(vkcell) { return vkcell.value()=="Chair"; } )[0].row().cell(1) → ? (expected 4.0)
Finally, you can also get Voodookit objects based on a new or already-Voodookit-enabled <table> element, or from tr or td elements within such a table:
?Test: $("#ex1").voodoo().constructor.toString().indexOf("function VkTable") >= 0 → ? (expected true)
On nonsucky browsers, you can do this test with nicer syntax:?Test: $.browser.msie? "VkTable" : $("#ex1").voodoo().constructor.name → ? (expected "VkTable")
?Test: $.browser.msie? "VkRow" : $("#ex1 tr:last").voodoo().constructor.name → ? (expected "VkRow")
?Test: $.browser.msie? "VkCell" : $("#ex1 tr:last td:first").voodoo().constructor.name → ? (expected "VkCell")
Row Addressing
HTML
<tr> <th>Item</th> <th>Price</th> </tr>
<tr> <td>Table</td> <td>100</td> </tr>
<tr> <td>Chair</td> <td>20.00</td> </tr>
<tr> <td>Sofa</td> <td>1200.00</td> </tr>
</table>
Javascript
cols: [
{ name: "item" },
{ name: "price" }
]
});
Demos and Tests
| Item | Price |
|---|---|
| Table | 100 |
| Chair | 20.00 |
| Sofa | 1200.00 |
?Test: vkex1a.cell(1, 0).row().cell("price") → ? (expected 20.00)
Similarly, you can get a cell's column, and query for another row's value in the same column:?Test: vkex1a.cell(1, 0).col().cell(0) → ? (expected "Table")
or start with a row lookup:?Test: vkex1a.row(1).cell("item") → ? (expected "Chair")
Column Data Types
HTML
<tr> <th>Item</th> <th>Price</th> </tr>
<tr> <td>Table</td> <td>100</td> </tr>
<tr> <td>Chair</td> <td>20.00</td> </tr>
<tr> <td>Sofa</td> <td>1200.00</td> </tr>
</table>
Javascript
Demos and Tests
| Item | Price |
|---|---|
| Table | 100 |
| Chair | 20.00 |
| Sofa | 1200.00 |
Demo: vkex2.col(1).sort()
As you can see, this doesn't work so well. We need to tell Voodookit that the second column contains floating-point numbers.
Column Data Types: Floating-point Numbers
HTML
<tr> <th>Item</th> <th>Price</th> </tr>
<tr> <td>Table</td> <td>100</td> </tr>
<tr> <td>Chair</td> <td>20.00</td> </tr>
<tr> <td>Sofa</td> <td>1200.00</td> </tr>
</table>
Javascript
cols: [
{ type: $.voodoo.types.string }, // default
{ type: $.voodoo.types.float }
]
});
Demos and Tests
| Item | Price |
|---|---|
| Table | 100 |
| Chair | 20.00 |
| Sofa | 1200.00 |
Demo: vkex3.col(1).sort()
Demo: vkex3.col(1).sortReverse()
Here we're passing a little bit of information to Voodookit about the columns in our table. The $.voodoo.types.float object is a Voodookit column type, whose main purpose is to provide a parseValue method, converting the table cell's HTML contents into a Javascript value. For this type, the parseValue method is simply a call to Javascript's built-in parseFloat function.
Declaring types ensures that any cell's value() function returns a Javascript object of the appropriate type. It also lets you use the type-correct operators for things like arithmetic, and the convenient built-in column arithmetic function:
?Test: vkex3.col(1).sum() → ? (expected 1320.00)
Column Names
HTML
<tr> <th>Item</th> <th>Price</th> </tr>
<tr> <td>Table</td> <td>100</td> </tr>
<tr> <td>Chair</td> <td>20.00</td> </tr>
<tr> <td>Sofa</td> <td>1200.00</td> </tr>
</table>
Javascript
cols: [
{ name: "item" },
{ name: "price",
type: $.voodoo.types.float }
]
});
Demos and Tests
| Item | Price |
|---|---|
| Table | 100 |
| Chair | 20.00 |
| Sofa | 1200.00 |
?Test: vkex3a.col("price").reduce( function(x,y) { return parseFloat(x)+parseFloat(y); } ) → ? (expected 1320.0)
Demo: vkex3a.col("price").sort()
Demo: vkex3a.col("price").sortReverse()
As an added bonus, when you provide a column name, Voodookit adds that name as a class to all td elements in that column:?Test: $("#ex3a tr:eq(2) td:eq(1)").hasClass("price") → ? (expected true)
Convenience Method for Sorting
HTML
<tr> <th>Name</th> <th>Age</th> <th>IQ</th> </tr>
<tr> <td>Alice</td> <td>2</td> <td>100</td> </tr>
<tr> <td>Bob</td> <td>1</td> <td>110</td> </tr>
<tr> <td>Charlie</td> <td>3</td> <td>90</td> </tr>
</table>
<style>#ex3b th { cursor: pointer; }</style>
Javascript
vkex3b.makeHeadersSort();
// synthesize a click event on the second th, so we can test that
// the table has been sorted by age.
$("#ex3b tr:first th:eq(1)").trigger("click");
Demos and Tests
| Name | Age | IQ |
|---|---|---|
| Alice | 2 | 100 |
| Bob | 1 | 110 |
| Charlie | 3 | 90 |
?Test: vkex3b.cell(1,1).value() → ? (expected 2)
Date and Time Data
HTML
<tr> <th>Task</th> <th>Deadline (raw)</th> <th>Deadline (date)</th> <th>Deadline (d+t)</th></tr>
<tr> <td>(3) get approval from client</td> <td>1245729600000</td> <td>1245729600000</td> <td>1245729600000</td> </tr>
<tr> <td>(1) sketch UI for floobulator</td> <td>1239163200000</td> <td>1239163200000</td> <td>1239163200000</td> </tr>
<tr> <td>(2) send example links to Tim</td> <td>1239336009000</td> <td>1239336009000</td> <td>1239336009000</td> </tr>
<tr> <td>(4) watch a bunch of TV</td> <td></td> <td></td> <td></td> </tr>
</table>
Javascript
cols: [
{ },
{ },
{ type: $.voodoo.types.date },
{ type: $.voodoo.types.dateTime }
]
});
vkex4.col(2).sort();
Demos and Tests
| Task | Deadline (raw) | Deadline (date) | Deadline (d+t) |
|---|---|---|---|
| (3) get approval from client | 1245729600000 | 1245729600000 | 1245729600000 |
| (1) sketch UI for floobulator | 1239163200000 | 1239163200000 | 1239163200000 |
| (2) send example links to Tim | 1239336009000 | 1239336009000 | 1239336009000 |
| (4) watch a bunch of TV |
?Test: vkex4.cell(0,2).value().constructor == Date → ? (expected true)
?Test: vkex4.cell(0,2).value().getTime() < vkex4.cell(1,2).value().getTime() → ? (expected true)
?Test: vkex4.cell(1,2).value().getTime() < vkex4.cell(2,2).value().getTime() → ? (expected true)
?Test: vkex4.cell(0,2).value().getTime() < vkex4.cell(2,2).value().getTime() → ? (expected true)
Demo: vkex4.col(2).sort()
Events
- vkRowScan — generated when a content row (a
tr element that contains one or more td elements) is
handled by Voo2do: on first review of the table content, and on subsequent
changes to row position (sorting). The event originates from the row
tr element. In this event's data hash:
- rowIndex: an integer indicating this row's position among all content rows in the table, starting from 0.
- vkRow: the VkRow object for this table row.
- vkChange — generated when the value of a cell is
changed. The event originates from the changed td element, and
bubbles up the document so you can also bind to a table and get
this event. Because you may be interested in changes within a column, the
vkChange event is also fired by the corresponding column, which
provides a bind method for this purpose. In this event's
data hash:
- oldValue: the cell's previous value.
- cell: the VkCell object for the affected cell. You can get the current (new) value by calling cell.value().
- vkRowAppend — generated when a new row is added to the table after the table's initial loading, e.g. with append or appendRow. Originates with the new tr element and bubbles up.
- vkRowDelete — generated before a row is deleted from the table using the deleterow() method. Originates with the delete tr element and bubbles up. Note that deleting a row will also trigger vkRowScan events to be triggered for all remaining rows; these are triggered after the vkRowDelete event.
- vkRowsReordered — generated once after a table's rows have been re-ordered, such as in a sort operation. Originates on the table object.
HTML
<tr> <th>First Name</th> <th>Last Name</th> </tr>
<tr> <td>Bob</td> <td>Jones</td> </tr>
<tr> <td>Alice</td> <td>Smith</td> </tr>
<tr> <td>Charlie</td> <td>Williams</td> </tr>
</table>
Javascript
// newly-created rows trigger the handler.
$("#ex5 tr").live("vkRowScan", function(e, data) {
var isOdd = data["rowIndex"] % 2;
$(this).css({ "background-color": isOdd? "skyblue": "lightgreen",
"font-weight": isOdd? "bold": "normal"
});
});
var vkex5 = $("#ex5").voodoo();
var vkex5_got_rowAppend = 0;
var vkex5_got_rowsReordered = 0;
$("#ex5").bind("vkRowAppend", function() {
vkex5_got_rowAppend += 1;
});
$("#ex5").bind("vkRowsReordered", function() {
vkex5_got_rowsReordered += 1;
});
vkex5.appendRow(["Dave","Sawyer"]);
vkex5.col(1).sort();
Demos and Tests
| First Name | Last Name |
|---|---|
| Bob | Jones |
| Alice | Smith |
| Charlie | Williams |
Demo: vkex5.col(0).sort()
Demo: vkex5.col(1).sort()
Did that bolding work correctly? We have to tolerate either a textual value ("bold") or a numeric one (700) for the font-weight property, depending on the browser used.?Test: $("#ex5 tr:eq(2)").css("font-weight")=="bold" || $("#ex5 tr:eq(2)").css("font-weight")==700 → ? (expected true)
?Test: $("#ex5 tr:eq(1)").css("font-weight")=="normal" || $("#ex5 tr:eq(1)").css("font-weight")==400 → ? (expected true)
?Test: vkex5_got_rowAppend → ? (expected 1)
?Test: vkex5_got_rowsReordered → ? (expected 1)
Renderers
In the examples so far, the values displayed in our HTML tables have simply been the default, stringified version of the cell's value. For a $.voodoo.types.date column, we see something like "06/23/2009". But what if you want your cell displayed in different format?
That's where renderers come in. Renderers determine how to display the values of cells in HTML. The renderer has access to the cell's VkCell object, allowing the rendering behavior to be based on the cell's value as well as its row, column, and table.
Voodookit includes the following built-in renderers. These are classes and should be instantiated using new. You can also build your own renderer—more on that later.
- $.voodoo.render.String
- $.voodoo.render.HtmlString
- $.voodoo.render.FloatHours *TODO*
- $.voodoo.render.Currency *TODO*
- $.voodoo.render.LocaleDate
- $.voodoo.render.LocateDateTime
Many of these renderers accept options that configure how they behave. Options can be passed in as an associative array parameter. Two options are supported by all renderers: default, which specifies the result to return when rendering an empty cell, and defaultValue which does something slightly different: it treats empty cells as having the given value — useful if you want to apply the same logic to generate the HTML for empty cells.
Note: The word default is a reserved word in Javascript. This option should probably be renamed; in any usage, be sure to quote the word default. For example, the object literal { default: "none" } will work in Firefox but cause a syntax error in Google Chrome, whereas { "default": "none" } works in all browsers.
Let's see how the default setting works using the String renderer.HTML
<tr> <th>Name</th> <th>Pizza Choices</th> </tr>
<tr> <td>Bob</td> <td>Pepperoni</td> </tr>
<tr> <td>Alice</td> <td> </td> </tr>
<tr> <td>Charlie</td> <td></td> </tr>
</table>
<p>Part 2:</p>
<table id="ex6_2">
<tr> <th>Name</th> <th>Pizza Choices</th> </tr>
<tr> <td>David</td> <td></td> </tr>
<tr> <td>Eliza</td> <td><b>peppers</b>--this is bad, should just be HTML</td> </tr>
</table>
Javascript
cols: [
{},
{ render: new $.voodoo.render.String({"default":"<i>unknown</i>"}) }
]
});
// on the second table, let's use the defaultValue option instead:
var vkex6_2 = $("#ex6_2").voodoo({
cols: [
{},
{ render: new $.voodoo.render.String({"defaultValue":"<i>unknown</i>"}) }
]
});
Demos and Tests
| Name | Pizza Choices |
|---|---|
| Bob | Pepperoni |
| Alice | |
| Charlie |
Part 2:
| Name | Pizza Choices |
|---|---|
| David | |
| Eliza | peppers--this is bad, should just be HTML |
?Test: $("#ex6 tr:last td:last").html().toLowerCase() → ? (expected "<i>unknown</i>")
Note that in our HTML, the value for Alice's pizza choices is a single space character— not considered empty by Voodookit. Hence it is rendered as a space:?Test: "["+$("#ex6 tr:eq(2) td:last").html().toLowerCase()+"]" → ? (expected "[ ]")
In the second table, we used the defaultValue option instead of default. What does this mean??Test: $("#ex6_2 tr:eq(1) td:last").html() → ? (expected "<i>unknown</i>")
Renderers with Editing
HTML
<tr> <th>Name</th> <th>Pizza Choices</th> <th>Paid?</th> </tr>
<tr> <td>Alice</td> <td>Artichokes</td> <td>1</td> </tr>
<tr> <td>Bob</td> <td>Pepperoni</td> <td>0</td> </tr>
<tr> <td>Charlie</td> <td></td> <td></td> </tr>
</table>
Javascript
cols: [
{},
{ render: new $.voodoo.render.TextField() },
{ type: $.voodoo.types.boolean,
render: new $.voodoo.render.CheckboxField() }
]
});
Demos and Tests
| Name | Pizza Choices | Paid? |
|---|---|---|
| Alice | Artichokes | 1 |
| Bob | Pepperoni | 0 |
| Charlie |
Demo: vkex7.col(0).sort()
Demo: vkex7.col(0).sortReverse()
Demo: vkex7.col(1).sort()
Demo: vkex7.col(1).sortReverse()
Demo: vkex7.col(2).sort()
Dealing with Edits
HTML
<tr> <th>Name</th> <th>Pizza Choices</th> <th>Slices Eaten</th> </tr>
<tr> <td>Alice</td> <td>Artichokes</td> <td>1</td> </tr>
<tr> <td>Bob</td> <td>Pepperoni</td> <td>0</td> </tr>
<tr> <td>Charlie</td> <td></td> <td>0</td> </tr>
</table>
Javascript
cols: [
{},
{ render: new $.voodoo.render.TextField(),
name: "pizza_choices"
},
{ type: $.voodoo.types.integer,
name: "numslices",
render: new $.voodoo.render.TextField() }
]
});
var vkex8_gotTableEvent = false;
var vkex8_gotRowEvent = false;
var vkex8_gotColumnEvent = false;
$("#ex8").bind("vkChange", function() {
vkex8_gotTableEvent = true;
});
$("#ex8 tr:eq(2)").bind("vkChange", function() {
vkex8_gotRowEvent = true;
});
vkex8.col("pizza_choices").bind("vkChange", function() {
vkex8_gotColumnEvent = true;
});
vkex8.cell(1,1).value("Sausage");
Demos and Tests
| Name | Pizza Choices | Slices Eaten |
|---|---|---|
| Alice | Artichokes | 1 |
| Bob | Pepperoni | 0 |
| Charlie | 0 |
?Test: vkex8_gotTableEvent → ? (expected true)
?Test: vkex8_gotRowEvent → ? (expected true)
?Test: vkex8_gotColumnEvent → ? (expected true)
The vkChange event is also used by renderers to update the rendered value when a cell's value is changed; try this increment operation to see how that works:
Adding Rows
- use a standard jQuery method on the table, like
$("#mytable").append("
") followed by a call to $("#mytable").voodoo.findNewRows(), or... - use the built-in $("#mytable").voodoo.append() method, which does the same thing.
HTML
<tr> <th>Name</th> <th>Pizza Choices</th> <th>Slices Eaten</th> </tr>
<tr> <td>Alice</td> <td>Artichokes</td> <td>1</td> </tr>
<tr> <td>Bob</td> <td>Pepperoni</td> <td>0</td> </tr>
<tr> <td>Charlie</td> <td>Chicken</td> <td>0</td> </tr>
</table>
Javascript
var vkex9_newRowIndex = null;
$("#ex9").bind("vkRowScan", function(e,data) {
vkex9_newRowText = data.vkRow.cell(0).value().toString();
vkex9_newRowIndex = data.rowIndex;
});
var vkex9 = $("#ex9").voodoo({
cols: [
{},
{ render: new $.voodoo.render.TextField() },
{ render: new $.voodoo.render.TextField() }
]
});
$("#ex9").voodoo().append("<tr><td>Dave</td><td>Olives</td><td>1</td></tr>");
Demos and Tests
| Name | Pizza Choices | Slices Eaten |
|---|---|---|
| Alice | Artichokes | 1 |
| Bob | Pepperoni | 0 |
| Charlie | Chicken | 0 |
?Test: vkex9_newRowIndex → ? (expected 3)
?Test: vkex9_newRowText → ? (expected "Dave")
So to recap, there's the two-step method: add table content however you want and then call findNewRows:Demo: $("#ex9").append("<tr><td>Ed</td><td>Spinach</td><td>2</td></tr>")
Or the append method built into Voodookit's VkTable class, which returns the VkTable object so it's chainable:But there's a shortcut...
- no arguments: appends a row with as many empty td elements as there are columns in the table so far.
- integer: appends a row with as many empty td elements as specified in the parameter.
- COMING SOON BUT NOT YET...
- array: appends a row with values as given in order in the parameter.
- object (hash): appends a row with as many columns as in the table so far, with some values filled in using column names or indices if available in the parameter.
HTML
<tr> <th>Name</th> <th>Pizza Choices</th> <th>Slices Eaten</th> </tr>
<tr> <td>Alice</td> <td>Artichokes</td> <td>1</td> </tr>
<tr> <td>Bob</td> <td>Pepperoni</td> <td>0</td> </tr>
<tr> <td>Charlie</td> <td>Chicken</td> <td>0</td> </tr>
</table>
Javascript
$("#ex9b").bind("vkRowScan", function(e,data) {
vkex9b_newRowTexts.push( data.vkRow.cell(0).value().toString() );
});
var vkex9b = $("#ex9b").voodoo({
cols: [
{ name: "name" },
{ render: new $.voodoo.render.TextField(), name: "pizza_choices" },
{ render: new $.voodoo.render.TextField(), name: "num_slices" }
]
});
$("#ex9b").voodoo().appendRow();
$("#ex9b").voodoo().appendRow(3);
$("#ex9b").voodoo().appendRow(["Eustace", "Broccoli", 2]);
Demos and Tests
| Name | Pizza Choices | Slices Eaten |
|---|---|---|
| Alice | Artichokes | 1 |
| Bob | Pepperoni | 0 |
| Charlie | Chicken | 0 |
?Test: $("#ex9b").voodoo().appendRow({ name: "Frank", 2: 7 }).is("tr") → ? (expected true)
?Test: vkex9b_newRowTexts[3] → ? (expected "")
?Test: vkex9b_newRowTexts[4] → ? (expected "")
?Test: vkex9b_newRowTexts[5] → ? (expected "Eustace")
?Test: vkex9b_newRowTexts[6] → ? (expected "Frank")
One more thing: the appendRow returns a jQuery object for the row, so you can do further manipulation of the row if you wish:Demo: $("#ex9b").voodoo().appendRow().find("input:first").focus() && false;
Deleting Rows
Add too many rows? Maybe you want to delete some. You can do this with standard jQuery/DOM methods if you wish: just .remove() a tr and all VoodooKit methods (row counts, positional cell coordinates, etc.) will keep working just fine, because VoodooKit treats the DOM (plus jQuery .data) as the primary underlying data structure whenever possible.
However, in most cases it is preferable to use the VkRow.deleterow() method, which will not only remove the appropriate tr from the table, but also generate some useful events:
- vkRowDelete on the row about to be deleted, while the VkRow object is still accessible.
- vkRowScan on all remaining rows in the table.
HTML
<tr> <th>Name</th> <th>Slices Eaten</th> <th>Delete?</th> </tr>
<tr> <td>Alice</td> <td>1</td> <td></td> </tr>
<tr> <td>Bob</td> <td>0</td> <td></td> </tr>
<tr> <td>Charlie</td> <td>0</td> <td></td> </tr>
</table>
Javascript
var vkex10_num_scans_per_name = {};
$("#ex10").bind("vkRowDelete", function(e) {
vkex10_name_deleted = $(e.target).voodoo().cell(0);
});
$("#ex10 tr").bind("vkRowScan", function(e, data) {
var r = data.vkRow;
var name = r.cell(0).value();
if(!vkex10_num_scans_per_name[ name ]) {
vkex10_num_scans_per_name[ name ] = 0;
}
vkex10_num_scans_per_name[ name ]++;
});
var vkex10 = $("#ex10").voodoo({
cols: [
{},
{ render: new $.voodoo.render.TextField() },
{ render: new $.voodoo.render.HtmlString({"default":"<a href='#' class='del'>del</a>"}) }
]
});
$("#ex10 a.del").live("click", function() {
$(this).parents("tr").voodoo().deleterow();
return false;
});
$("#ex10 a.del:first").trigger("click");
Demos and Tests
| Name | Slices Eaten | Delete? |
|---|---|---|
| Alice | 1 | |
| Bob | 0 | |
| Charlie | 0 |
?Test: vkex10.numContentRows() → ? (expected 2)
?Test: vkex10_name_deleted → ? (expected "Alice")
The row we deleted should have 1 row scan, from the initial load:?Test: vkex10_num_scans_per_name["Alice"] → ? (expected 1)
Remaining rows should have 2 row scans, one from initial load and one after the deletion of the Alice row:?Test: vkex10_num_scans_per_name["Bob"] → ? (expected 2)
?Test: vkex10_num_scans_per_name["Charlie"] → ? (expected 2)
In some cases, you might not want to immediately remove the deleted tr from the document. For example, you might want to animate the removal effect. You can do this by calling prepToDelete instead of delete— prepToDelete will ready the row for deletion and send the appropriate events, and like delete will return a jQuery object wrapping the relevant tr. But unlike delete, prepToDelete counts on you to make a final call to remove the row when you're done. (It is important that you actually do remove the row from its containing table; until you complete that step, the VkTable object's state, such as the result of numRows(), will be inconsistent with the events that have been generated.)Demo: $("#ex10 tr:eq(1)").voodoo().prepDelete().fadeOut(function(){ $(this).remove(); }) && false;
Computed Columns
Sometimes you want to calculate a value for a row. For example, if you are tracking tasks you might have estimated time and a time elapsed columns entered by the user. What if you want to also display a time remaining column?
Voodookit makes this easy by letting you define a computed column. Computed columns are like regular columns in your table, but instead of reading their value from the content of a <td> element, their value is calculated according to a formula you specify.
Computed cell values can be based on any non-computed columns in the current row, as well arbitrary Javascript. Values based on other columns will automatically change if their parameters are changed. And of course, cells in computed columns still generate the same events as other cells.
HTML
<tr> <th>Name</th> <th>Slices Eaten</th> <th>% of 8-slice Pizza</th> </tr>
<tr> <td>Alice</td> <td>1</td> <td>?</td> </tr>
<tr> <td>Bob</td> <td>0</td> <td>.15</td> </tr>
<tr> <td>Charlie</td> <td>0</td> <td></td> </tr>
</table>
Javascript
cols: [
{ name: "person" },
{ name: "num_slices",
render: new $.voodoo.render.TextField(),
type: $.voodoo.types.integer
},
{ name: "pct_of_za",
type: $.voodoo.types.float,
compute_from_cols: [ "num_slices" ],
compute_function: function(num_slices) {
return num_slices / 8;
}
},
]
});
Demos and Tests
| Name | Slices Eaten | % of 8-slice Pizza |
|---|---|---|
| Alice | 1 | ? |
| Bob | 0 | .15 |
| Charlie | 0 |
?Test: vkex11.col("num_slices").is_computed() → ? (expected false)
?Test: vkex11.col("pct_of_za").is_computed() → ? (expected true)
?Test: vkex11.cell(0,2).value() → ? (expected 0.125)
But wait, there's more! Computed columns listen for vkChange events on their neighbor cells specified in the compute_from_cols parameter. So if we change one of the num_slices cells:?Test: vkex11.cell(1,1).value(3) → ? (expected 3)
?Test: vkex11.cell(1,2).value() → ? (expected 0.375)
Try it yourself... just edit one of the central column values and watch the right column change in response!HTML Templating Renderers
Sometimes it's simpler to specify how a column might look by writing an HTML template, instead of a renderer function. Voodookit includes a simple template processor that, given a HTML-like template string, compiles a Renderer class that executes the given template. The template can refer to other cells in the same row as variables, and can execute some basic javascript code.
HTML
<tr> <th>Name</th> <th>Slices Eaten</th> <th>Story</th> </tr>
<tr> <td>Alice</td> <td>2</td> <td></td> </tr>
<tr> <td>Bob</td> <td>3</td> <td></td> </tr>
<tr> <td>Charlie</td> <td>0</td> <td></td> </tr>
</table>
Javascript
cols: [
{ name: "person" },
{ name: "num_slices",
render: new $.voodoo.render.TextField(),
type: $.voodoo.types.integer
},
{ name: "story",
render: $.voodoo.makeRen.template("<<person>> <<if num_slices>>ate <<num_slices>> slice(s).<<else>>is STARVING!<</if>>")
},
]
});
Demos and Tests
| Name | Slices Eaten | Story |
|---|---|---|
| Alice | 2 | |
| Bob | 3 | |
| Charlie | 0 |
?Test: vkex12.cell(0,2).$td.text() → ? (expected "Alice ate 2 slice(s).")
?Test: vkex12.cell(2,2).$td.text() → ? (expected "Charlie is STARVING!")
Note that currently, templates will only automatically re-render when their cell's value changes, but not when other values that may have been used in the template change.Alternate Representations of the Table Data
Voodookit includes a flexible facility to generate an alternate representation of the data in your table. This allows you to take a VkTable in your document and, based on its current state including any recent user modifications, get a representation of that table. You can use it to quickly get a JSON serialization of your table, or a custom representation specific to your needs.
HTML
<tr> <th>Name</th> <th>Slices Eaten</th> </tr>
<tr> <td>Alice</td> <td>2</td> </tr>
<tr> <td>Bob</td> <td>3</td> </tr>
<tr> <td>Charlie</td> <td>0</td> </tr>
</table>
<h4>Rendering output:</h4>
<div style="background:linen; border: 1px solid blue;" id="ex13_altrender"> </div>
Javascript
cols: [
{ name: "person",
render: new $.voodoo.render.TextField()
},
{ name: "num_slices",
render: new $.voodoo.render.TextField(),
type: $.voodoo.types.integer
}
]
});
Demos and Tests
| Name | Slices Eaten |
|---|---|
| Alice | 2 |
| Bob | 3 |
| Charlie | 0 |
Rendering output:
Demo: $("#ex13_altrender").text(vkex13.altRender("json")) && false
?Test: vkex13.altRender("json").indexOf("\"Charlie\",\"0\"") > 0 → ? (expected true)
?Test: vkex13.altRender("json").charAt(0) → ? (expected "[")
Custom AltRendering
- $.voodoo.VkTable
- $.voodoo.VkColumn
- $.voodoo.VkRow
- $.voodoo.VkCell
HTML
<tr> <th>Name</th> <th>Slices Eaten</th> </tr>
<tr> <td>Alice</td> <td>2</td> </tr>
<tr> <td>Bob</td> <td>3</td> </tr>
<tr> <td>Charlie</td> <td>0</td> </tr>
</table>
<h4>Rendering output:</h4>
<div style="background:linen; border: 1px solid blue;" id="ex14_altrender"> </div>
Javascript
cols: [
{ name: "person",
render: new $.voodoo.render.TextField()
},
{ name: "num_slices",
render: new $.voodoo.render.TextField(),
type: $.voodoo.types.integer
}
]
});
$.voodoo.VkTable.prototype.as_printable = function() {
var result = $("<table>");
result.append(this.$table.find("tr:has(th):eq(0)").clone());
this.rows().map(function() { result.append(this.as_printable()); });
return result;
};
$.voodoo.VkRow.prototype.as_printable = function() {
var result = $("<tr>");
this.cells().map(function() { result.append(this.as_printable()); });
return result;
};
$.voodoo.VkCell.prototype.as_printable = function() {
var result = $("<td>").text(this.isEmpty? "" : this.value() );
return result;
};
$("#ex14_altrender").html(vkex14.altRender("printable"));
Demos and Tests
| Name | Slices Eaten |
|---|---|
| Alice | 2 |
| Bob | 3 |
| Charlie | 0 |
Rendering output:
?Test: $("#ex14_altrender td:eq(2)").text() → ? (expected "Bob")
Demo: $("#ex14_altrender").html(vkex14.altRender("printable")) && false
Demo: $("#ex14_altrender").text(vkex14.altRender("json")) && false
Paging
HTML
<tr> <th>Page</th> <th>Item Number</th> </tr>
</table>
<table id="ex15_staticpage" style="display:none">
<tr> <td>0</td> <td>0.1</td> </tr>
<tr> <td>0</td> <td>0.2</td> </tr>
<tr> <td>0</td> <td>0.3</td> </tr>
</table>
Javascript
// first, let's load rows from a hidden table that's in the document.
vkex15.loadRowsFromTable($("#ex15_staticpage"));
// in between pages, we'll want to clear the table:
vkex15.clear();
// now we can load multiple pages, even:
vkex15.loadRowsFromTable($("#ex15_staticpage"));
vkex15.loadRowsFromTable($("#ex15_staticpage"));
Demos and Tests
| Page | Item Number |
|---|
?Test: vkex15.cell(0,0).$td.text() → ? (expected "0")
?Test: vkex15.cell(4,1).$td.text() → ? (expected "0.2")
This loading should be non-destructive to the original table:?Test: $("#ex15_staticpage td:eq(3)").text() → ? (expected "0.2")
Try it yourself:Demo: vkex15.loadRowsFromTable($("#ex15_staticpage"));
Demo: vkex15.clear();
Paging with Ajax
HTML
<tr> <th>Page</th> <th>Item Number</th> </tr>
</table>
<a id="ex16_pagelink" href="/voodookit/tests/example_page_data/">load next page</a>
Javascript
var pageNum = 0;
var pageUrl = $("#ex16_pagelink").attr("href");
$("#ex16_pagelink").bind("click", function() {
vkex16.loadRowsFromUrl(pageUrl+"?page="+pageNum, {}, function(data, textStatus) { vkex16.clear(); pageNum++; });
return false;
});
Demos and Tests
| Page | Item Number |
|---|
Demo: vkex16.loadRowsFromUrl($("#ex16_pagelink").attr("href"));