TaffyDB 是一个免费开源的 JavaScript 库,用于在 Web 上实现一个轻量级的数据访问层,也就是一个简单的数据库。
将要开展的工作
TaffyDB抽取了JavaScript中的数据操作的“艰难”部分。它提供了对数据集(合)的插入、更新、删除、排序和过滤方法,与SQL的使用的方式大致相同。但如何利用这一优势来构建数据密集型应用程序呢?如何利用TaffyDB以尽量减少和简化代码呢?
在本教程中,将一步一步来建立一个简单的数据表格应用:以TaffyDB为数据引擎,该应用将渲染一个表格(table),并给出选项以便根据自己的喜好自定义表格。
- 先决条件
- 一个简单表格应用(25行数据)
- 添加列名
- 自定义列名
- 添加列排序(利用TaffyDB)
- 自定义列/记录级交互
- 设想可能性
1、先决条件
对于本文所涉例子,需要一些支撑代码(包):
- TaffyDB的taffy.js是必需的。可在这里下载最新版本。
- 在body标签的onLoad方法中调用init()函数来设置表格。
- 需使用“binder”函数来实现应用和TaffyDB数据集的绑定。绑定函数最基本的,其中设置所有变量和闭包(closures)使工作顺利。下面是基本绑定(器)函数的框架(Shell):
//binder function that takes a config and taffycollection varappBinder = function(config,taffycollection) { // app object to contain your custom methods var app = { myMethod:function () { } } // combine taffycollection with your app and return it app = TAFFY.mergeObj(app,taffycollection); return app; |
- 需要一Taffy集合。Taffy集合为一JavaScript数组对象,每一对象包含一条记录。这些集合可以硬编码为.js文件,由应用动态创建,也可通过AJAX和JSON从Web服务导入。
在本文的例子中,会常用的下面这个简单的水果数组:
fruits = [{food:"Apple",water:84,fiber:2.3,fat:0,protein:0.4,sugar:11.8}, {food:"Apricot",water:87,fiber:2.1,fat:0,protein:1,sugar:8}, {food:"Banana",water:76,fiber:2.7,fat:0,protein:1.2,sugar:20.4}, {food:"Fig",water:80,fiber:2,fat:0,protein:1,sugar:19}, {food:"Grapefruit, Red",water:90,fiber:1.4,fat:0,protein:0.9,sugar:6.6}, {food:"Guava",water:81,fiber:5.3,fat:0,protein:1.0,sugar:17.0}, {food:"Kiwi Fruit",water:84,fiber:2.1,fat:0,protein:1.1,sugar:8.8}, {food:"Lemon",water:96,fiber:1.8,fat:0,protein:0,sugar:3}, {food:"Lime",water:91,fiber:0.3,fat:0,protein:0,sugar:7}, {food:"Mandarin / Tangerine",water:88,fiber:1.9,fat:0,protein:0.9,sugar:9.5}, {food:"Mango",water:84,fiber:1,fat:0,protein:0,sugar:15}, {food:"Orange",water:87,fiber:1.8,fat:0,protein:1,sugar:10.6}, {food:"Papaya",water:91,fiber:0.6,fat:0,protein:0,sugar:8}, {food:"Passion Fruit",water:88,fiber:3.3,fat:0.4,protein:2.6,sugar:5.8}, {food:"Peach",water:89,fiber:1.4,fat:0,protein:1,sugar:7.9}, {food:"Pear",water:86,fiber:2.1,fat:0,protein:0.3,sugar:11.5}, {food:"Plum",water:84,fiber:2.2,fat:0,protein:0.8,sugar:9.6}]; |
- 最后,需要一页面中的DIV,在其中渲染表格。该示例div的ID为displayGridDiv。
2、一个简单的表格应用(25行以下数据)
可将上文的水果(fruit)集合很容易地变成如下的表格:
0 |
2.3 |
Apple |
0.4 |
11.8 |
84 |
0 |
2.1 |
Apricot |
1 |
8 |
87 |
0 |
2.7 |
Banana |
1.2 |
20.4 |
76 |
0 |
2 |
Fig |
1 |
19 |
80 |
0 |
1.4 |
Grapefruit, Red |
0.9 |
6.6 |
90 |
0 |
5.3 |
Guava |
1 |
17 |
81 |
0 |
2.1 |
Kiwi Fruit |
1.1 |
8.8 |
84 |
0 |
1.8 |
Lemon |
0 |
3 |
96 |
0 |
0.3 |
Lime |
0 |
7 |
91 |
0 |
1.9 |
Mandarin / Tangerine |
0.9 |
9.5 |
88 |
0 |
1 |
Mango |
0 |
15 |
84 |
0 |
1.8 |
Orange |
1 |
10.6 |
87 |
0 |
0.6 |
Papaya |
0 |
8 |
91 |
0.4 |
3.3 |
Passion Fruit |
2.6 |
5.8 |
88 |
0 |
1.4 |
Peach |
1 |
7.9 |
89 |
0 |
2.1 |
Pear |
0.3 |
11.5 |
86 |
0 |
2.2 |
Plum |
0.8 |
9.6 |
84 |
该表格使用一个非常简单的表格“打印”程序创建。该gridPrinter()函数接受TaffyDB水果(fruit)集合,并增加了一“打印”集合到一个页面DIV的方法。代码如下:
vargridPrinter = function (config,taffyCollection) { var app = { // the print method to render the table print: function(){ // get the column names from the first record in the TaffyDB Collection config.columns = TAFFY.getObjectKeys(this.first()); var thebody = document.createElement("tbody"); // loop over each record using TaffyDB‘s forEach, add new row this.forEach(function(r, c){ var newRow = document.createElement("tr"); // loop over each value in the record, add to row for (var x = 0; x < config.columns.length; x++) { var newCell = document.createElement("td"); newCell.appendChild(document.createTextNode(r[config.columns[x]])); newRow.appendChild(newCell); } // add row to body of table thebody.appendChild(newRow); }); // create table and append body var thetable = document.createElement("table"); thetable.appendChild(thebody); thetable.border=1; // clear div and append new table document.getElementById(config.containerID).innerhtml = ""; document.getElementById(config.containerID).appendChild(thetable); } } app = TAFFY.mergeObj(app,taffyCollection); return app; } |
注意:foreach是TaffyDB的方法,遍历集合中的记录,并应用一个函数。要了解更多有关如何设置使用foreach和过滤指定记录的方法,请点击这里。
注:taffy.getObjectKeys()为TaffyDB应用函数。它得到一JavaScript对象的键(key)名。在应用中科使用许多类似的函数。更多信息请点击这里。
为调用gridPrinter()可在onload中使用一init()函数:
varinit = function() { // invoke your gridPrinter() and pass in the div ID and the new taffy collection fruit = gridPrinter({ containerID: "displayGridDiv" },TAFFY(fruits)); // call print fruit.print(); } |
- 添加列名
希望自己的数据是可理解的,因此需要列名。要添加列名,需修改gridPrinter(),在表格头部增加一行,新增内容以前缀//#NEW#注释:
vargridPrinter = function (config,taffyCollection) { var app = { print: function(){ var thebody = document.createElement("tbody"); config.columns = TAFFY.getObjectKeys(this.first()); var newRow = document.createElement("tr"); // #NEW# loop over the column names and add cells to a new row with each name for (var x = 0; x < config.columns.length; x++) { var newCell = document.createElement("td"); newCell.appendChild(document.createTextNode(config.columns[x])); newRow.appendChild(newCell); } // #NEW# add the column names to the first row of the grid thebody.appendChild(newRow);
this.forEach(function(r, c){ var newRow = document.createElement("tr"); for (var x = 0; x < config.columns.length; x++) { var newCell = document.createElement("td"); newCell.appendChild(document.createTextNode(r[config.columns[x]])); newRow.appendChild(newCell); } thebody.appendChild(newRow); }); var thetable = document.createElement("table"); thetable.appendChild(thebody); thetable.border=1; document.getElementById(config.containerID).innerHTML = ""; document.getElementById(config.containerID).appendChild(thetable); } } app = TAFFY.mergeObj(app,taffyCollection); return app; } |
新表格如下:
脂肪 |
纤维 |
食品 |
蛋白质 |
糖 |
水 |
0 |
2.3 |
苹果 |
0.4 |
11.8 |
84 |
0 |
2.1 |
杏 |
一 |
8 |
87 |
0 |
2.7 |
香蕉 |
1.2 |
20.4 |
76 |
0 |
2 |
图 |
一 |
19 |
80 |
0 |
1.4 |
葡萄柚,红 |
0.9 |
6.6 |
90 |
0 |
5.3 |
番石榴 |
一 |
17 |
81 |
0 |
2.1 |
猕猴桃 |
1.1 |
8.8 |
84 |
0 |
1.8 |
柠檬 |
0 |
三 |
96 |
0 |
0.3 |
石灰 |
0 |
7 |
91 |
0 |
1.9 |
国语/橘 |
0.9 |
9.5 |
88 |
0 |
一 |
芒果 |
0 |
15 |
84 |
0 |
1.8 |
橙 |
一 |
10.6 |
87 |
0 |
0.6 |
木瓜 |
0 |
8 |
91 |
0.4 |
3.3 |
百香果 |
2.6 |
5.8 |
88 |
0 |
1.4 |
桃 |
一 |
7.9 |
89 |
0 |
2.1 |
梨 |
0.3 |
11.5 |
86 |
0 |
2.2 |
梅 |
0.8 |
9.6 |
84 |
将采用与上一例子中相同的init()函数:
varinit = function () { fruit = gridPrinter({ containerID: "displayGridDiv" },TAFFY(fruits)); fruit.print(); } |
3、添加列名
希望自己的数据是可理解的,因此需要列名。要添加列名,需修改gridPrinter(),在表格头部增加一行,新增内容以前缀//#NEW#注释:
vargridPrinter = function (config,taffyCollection) { var app = { print: function(){ var thebody = document.createElement("tbody"); config.columns = TAFFY.getObjectKeys(this.first()); var newRow = document.createElement("tr"); // #NEW# loop over the column names and add cells to a new row with each name for (var x = 0; x < config.columns.length; x++) { var newCell = document.createElement("td"); newCell.appendChild(document.createTextNode(config.columns[x])); newRow.appendChild(newCell); } // #NEW# add the column names to the first row of the grid thebody.appendChild(newRow);
this.forEach(function(r, c){ var newRow = document.createElement("tr"); for (var x = 0; x < config.columns.length; x++) { var newCell = document.createElement("td"); newCell.appendChild(document.createTextNode(r[config.columns[x]])); newRow.appendChild(newCell); } thebody.appendChild(newRow); }); var thetable = document.createElement("table"); thetable.appendChild(thebody); thetable.border=1; document.getElementById(config.containerID).innerHTML = ""; document.getElementById(config.containerID).appendChild(thetable); } } app = TAFFY.mergeObj(app,taffyCollection); return app; } |
新表格如下:
脂肪 |
纤维 |
食品 |
蛋白质 |
糖 |
水 |
0 |
2.3 |
苹果 |
0.4 |
11.8 |
84 |
0 |
2.1 |
杏 |
一 |
8 |
87 |
0 |
2.7 |
香蕉 |
1.2 |
20.4 |
76 |
0 |
2 |
图 |
一 |
19 |
80 |
0 |
1.4 |
葡萄柚,红 |
0.9 |
6.6 |
90 |
0 |
5.3 |
番石榴 |
一 |
17 |
81 |
0 |
2.1 |
猕猴桃 |
1.1 |
8.8 |
84 |
0 |
1.8 |
柠檬 |
0 |
三 |
96 |
0 |
0.3 |
石灰 |
0 |
7 |
91 |
0 |
1.9 |
国语/橘 |
0.9 |
9.5 |
88 |
0 |
一 |
芒果 |
0 |
15 |
84 |
0 |
1.8 |
橙 |
一 |
10.6 |
87 |
0 |
0.6 |
木瓜 |
0 |
8 |
91 |
0.4 |
3.3 |
百香果 |
2.6 |
5.8 |
88 |
0 |
1.4 |
桃 |
一 |
7.9 |
89 |
0 |
2.1 |
梨 |
0.3 |
11.5 |
86 |
0 |
2.2 |
梅 |
0.8 |
9.6 |
84 |
将采用与上一例子中相同的init()函数:
varinit = function () { fruit = gridPrinter({ containerID: "displayGridDiv" },TAFFY(fruits)); fruit.print(); } |
4、自定义列名
有名称是伟大的,但为了使他们有用的,需要能够控制列的顺序和应用自定义名称。下面是一使用自定义名称的表格输出代码示例。要开始学习init()中配置对象的更多内容。
新的表格:
Fruit |
water |
fiber |
fat |
protein |
sugar |
Apple |
84 |
2.3 |
0 |
0.4 |
11.8 |
Apricot |
87 |
2.1 |
0 |
1 |
8 |
Banana |
76 |
2.7 |
0 |
1.2 |
20.4 |
Fig |
80 |
2 |
0 |
1 |
19 |
Grapefruit, Red |
90 |
1.4 |
0 |
0.9 |
6.6 |
Guava |
81 |
5.3 |
0 |
1 |
17 |
Kiwi Fruit |
84 |
2.1 |
0 |
1.1 |
8.8 |
Lemon |
96 |
1.8 |
0 |
0 |
3 |
Lime |
91 |
0.3 |
0 |
0 |
7 |
Mandarin / Tangerine |
88 |
1.9 |
0 |
0.9 |
9.5 |
Mango |
84 |
1 |
0 |
0 |
15 |
Orange |
87 |
1.8 |
0 |
1 |
10.6 |
Papaya |
91 |
0.6 |
0 |
0 |
8 |
Passion Fruit |
88 |
3.3 |
0.4 |
2.6 |
5.8 |
Peach |
89 |
1.4 |
0 |
1 |
7.9 |
Pear |
86 |
2.1 |
0 |
0.3 |
11.5 |
Plum |
84 |
2.2 |
0 |
0.8 |
9.6 |
请注意,现在所谓的食物(Food)列被称为果实(Fruit)?这是通过一个带有名称和列数组中显示值的自定义对象实现的。该gridPrinter()接受该对象,并使用列名对应的显示值。下面是gridPrinter()的变型:
vargridPrinter = function (config,taffyCollection) { var app = { print: function(){ var thebody = document.createElement("tbody");
// #NEW# use getObjectKeys only if config.columns wasn‘t defined config.columns = config.columns || TAFFY.getObjectKeys(this.first()); var newRow = document.createElement("tr"); for (var x = 0; x < config.columns.length; x++) { var newCell = document.createElement("td"); // #NEW# use the display value if given in the columns config newCell.appendChild(document.createTextNode( TAFFY.isObject(config.columns[x]) ? config.columns[x]["display"] : config.columns[x] )); newRow.appendChild(newCell); } thebody.appendChild(newRow); this.forEach(function(r, c){ var newRow = document.createElement("tr"); for (var x = 0; x < config.columns.length; x++) { var newCell = document.createElement("td"); // #NEW# use the name value if given in the columns config newCell.appendChild( (TAFFY.isObject(config.columns[x]) && !TAFFY.isUndefined(config.columns[x].name)) ? document.createTextNode(r[config.columns[x].name]) : TAFFY.isString(config.columns[x]) ? document.createTextNode(r[config.columns[x]]) : document.createTextNode("") ); newRow.appendChild(newCell); } thebody.appendChild(newRow); }); var thetable = document.createElement("table"); thetable.appendChild(thebody); thetable.border=1; document.getElementById(config.containerID).innerHTML = ""; document.getElementById(config.containerID).appendChild(thetable); } } app = TAFFY.mergeObj(app,taffyCollection); return app; } |
注:JavaScript的||(有时称为逻辑或)操作符可提供变量默认值。在此若定义了config.columns则通过config.columns获得列名,否则取TaffyDB集合的列名。
注:JavaScript的三元运算符也经常用在这些例子中。这使得表达式易于计算,并基于if表达式的真假在两个选项中选择。这通常是if语句很好的替代。格式为:表达式?true_option:false_option。
这时候需要init()传递更多的信息。对于自定义的列,需要名称和显示值,以确定哪些列使用什么名称显示。
varinit = function () { fruit = gridPrinter({ containerID: "displayGridDiv", // #NEW# pass in a custom array of columns with one custom column name columns: [{ name: "food", display: "Fruit" }, "water", "fiber", "fat", "protein", "sugar"] },TAFFY(fruits)); fruit.print(); } |
5、添加TaffyDB列排序
增强gridPrinter()为其添加排序对TaffyDB而言仅是一瞬间。在本例中,可按果实(Fruit)排序。尝试点击它按降序排序,再次点击则按升序排序:
Fruit |
water |
fiber |
fat |
protein |
sugar |
Apple |
84 |
2.3 |
0 |
0.4 |
11.8 |
Apricot |
87 |
2.1 |
0 |
1 |
8 |
Banana |
76 |
2.7 |
0 |
1.2 |
20.4 |
Fig |
80 |
2 |
0 |
1 |
19 |
Grapefruit, Red |
90 |
1.4 |
0 |
0.9 |
6.6 |
Guava |
81 |
5.3 |
0 |
1 |
17 |
Kiwi Fruit |
84 |
2.1 |
0 |
1.1 |
8.8 |
Lemon |
96 |
1.8 |
0 |
0 |
3 |
Lime |
91 |
0.3 |
0 |
0 |
7 |
Mandarin / Tangerine |
88 |
1.9 |
0 |
0.9 |
9.5 |
Mango |
84 |
1 |
0 |
0 |
15 |
Orange |
87 |
1.8 |
0 |
1 |
10.6 |
Papaya |
91 |
0.6 |
0 |
0 |
8 |
Passion Fruit |
88 |
3.3 |
0.4 |
2.6 |
5.8 |
Peach |
89 |
1.4 |
0 |
1 |
7.9 |
Pear |
86 |
2.1 |
0 |
0.3 |
11.5 |
Plum |
84 |
2.2 |
0 |
0.8 |
9.6 |
本例子是利用TaffyDB的OrderBy排序方法作为快捷方式来添加排序功能。要其实现方法为在gridPrinter()应用对象内增加一个名为sortColumn()的新方法,在点击水果?(Fruit)时调用该方法。
var
var
var
// #NEW# check to see what was last sorted // #NEW# if nothing was sorted or this column was last sorted, sort desc if
// #NEW# call TaffyDB‘s orderBy this
this
// #NEW# call TaffyDB‘s orderBy this
this
this
var
var for var
// #NEW# if sortable is true make column sortable if
// #NEW# add the colName variable to the table cell
// #NEW# call app.sortColumn since the functions "this" // #NEW# now points to the cell
this
var for var
var
return
|
注:app.sortColumn()使用TaffyDB的逻辑排序算法。虽有其他选择,但这种分类方法产生对绝大多数人有用的结果,尤其对于包含数字和字母的数据并采用所期望的与桌面操作系统相同的排序方式。
注意:当为gridPrinter()print方法添加一个onclick事件时,需要使用app.“methodName()”调用TaffyDB方法。这是因为JavaScript“this”“对象指向Click事件,而非TaffyDB。
现在需要为水果(Fruit)栏添加一个sort::true的标志,以确定它为可排序列:
var
// #NEW# add sortable:true to your custom fruit column
|
6、自定义列/纪录级交互
令人惊讶是可以轻松地添加自定义列和记录级交互(功能)。只要在gridPrinter()中添加callme()方法,在init()函数中直接增加新的列很容易。这样就使得gridPrinter()具有通用性:完全控制输出。
本例中添加了行号列和删除按钮列。点击删除按钮时将删除该行记录,还可以通过点击Fruit列重新排序。
# |
Fruit |
water |
fiber |
fat |
protein |
sugar |
Delete |
1 |
Apple |
84 |
2.3 |
0 |
0.4 |
11.8 |
Delete |
2 |
Apricot |
87 |
2.1 |
0 |
1 |
8 |
Delete |
3 |
Banana |
76 |
2.7 |
0 |
1.2 |
20.4 |
Delete |
4 |
Fig |
80 |
2 |
0 |
1 |
19 |
Delete |
5 |
Grapefruit, Red |
90 |
1.4 |
0 |
0.9 |
6.6 |
Delete |
6 |
Guava |
81 |
5.3 |
0 |
1 |
17 |
Delete |
7 |
Kiwi Fruit |
84 |
2.1 |
0 |
1.1 |
8.8 |
Delete |
8 |
Lemon |
96 |
1.8 |
0 |
0 |
3 |
Delete |
9 |
Lime |
91 |
0.3 |
0 |
0 |
7 |
Delete |
10 |
Mandarin / Tangerine |
88 |
1.9 |
0 |
0.9 |
9.5 |
Delete |
11 |
Mango |
84 |
1 |
0 |
0 |
15 |
Delete |
12 |
Orange |
87 |
1.8 |
0 |
1 |
10.6 |
Delete |
13 |
Papaya |
91 |
0.6 |
0 |
0 |
8 |
Delete |
14 |
Passion Fruit |
88 |
3.3 |
0.4 |
2.6 |
5.8 |
Delete |
15 |
Peach |
89 |
1.4 |
0 |
1 |
7.9 |
Delete |
16 |
Pear |
86 |
2.1 |
0 |
0.3 |
11.5 |
Delete |
17 |
Plum |
84 |
2.2 |
0 |
0.8 |
9.6 |
Delete |
为了正常工作,需要在列数组配置(对象)添加对callme()的支持。这些方法在每行渲染时被调用,结果将插入该行及其列所在的单元。
如下为新的表格打印(输出)方法:
vargridPrinter = function (config,taffyCollection) { var app = { sortColumn:function (col) { var s = {}; if (((this.lastSort) ? this.lastSort == col : s[col] = "logicaldesc"; this.orderBy([s]); this.lastSort = col + ‘desc‘ } else { s[col] = "logical"; this.orderBy([col]); this.lastSort = col; } this.print(); }, print: function(){ var thebody = document.createElement("tbody"); config.columns = config.columns || TAFFY.getObjectKeys(this.first()); var newRow = document.createElement("tr"); for (var x = 0; x < config.columns.length; x++) { var newCell = document.createElement("td"); newCell.appendChild(document.createTextNode( TAFFY.isObject(config.columns[x]) ? config.columns[x]["display"] : config.columns[x] )); if (TAFFY.isObject(config.columns[x]) && !TAFFY.isUndefined(config.columns[x].sortable) && config.columns[x].sortable) { newCell.colName = config.columns[x]["name"]; newCell.onclick = function () { app.sortColumn(this.colName); } } newRow.appendChild(newCell); } thebody.appendChild(newRow); this.forEach(function(r, c){ var newRow = document.createElement("tr"); for (var x = 0; x < config.columns.length; x++) { var newCell = document.createElement("td"); newCell.appendChild( (TAFFY.isObject(config.columns[x]) && !TAFFY.isUndefined(config.columns[x].name)) ? document.createTextNode(r[config.columns[x].name]) : // #NEW# add condition for custom columns with callme methods (TAFFY.isObject(config.columns[x]) && !TAFFY.isUndefined(config.columns[x].callme)) ? config.columns[x].callme(r,c) : // #NEW# otherwise add column as normal TAFFY.isString(config.columns[x]) ? document.createTextNode(r[config.columns[x]]) : document.createTextNode("") ); newRow.appendChild(newCell); } thebody.appendChild(newRow); }); var thetable = document.createElement("table"); thetable.appendChild(thebody); thetable.border=1; document.getElementById(config.containerID).innerHTML = ""; document.getElementById(config.containerID).appendChild(thetable); } } app = TAFFY.mergeObj(app,taffyCollection); return app; } |
现在需要为callme()方法定义两个新列。请记住,为在单元格中显示,需要返回要显示的内容。
varinit = function () { fruit = gridPrinter({ containerID: "displayGridDiv", columns: [ // #NEW# create new column with a callme function to render row number { display: "#", callme: function(r, c){ // #NEW# create text node to display row number return document.createTextNode((c+1)); } }, { name: "food", display: "Fruit", sortable: true }, "water", "fiber", "fat", "protein", "sugar", // #NEW# create new column with a callme function to render delete { display: "Delete", callme: function(r, c){ // #NEW# create a tag to return to the print function var op = document.createElement("strong"); op.onclick = function(){ // #NEW# on click of the strong tag remove record and reprint grid fruit.remove(c); fruit.print(); }; op.appendChild(document.createTextNode("Delete")); return op; } }] },TAFFY(fruits)); fruit.print(); } |
注意:传入callme()中的r和c的值是由TaffyDB提高的。r是该行的记录对象而c是行号。
7、预想的可能性
可以很容易为gridPrinter()不断加入新功能,采用这种方法有可能建立一个难以预想的复杂应用,简洁地将数据管理和界面组件分离。
想了解更多的东西请登录TaffyDB.com,在那里你会找到入门篇章、FAQ,以及邮件列表。
原文链接:http://taffydb.com/JavaScript_table_grid_application.htm