[size=large][align=center][b]PandaJS 使用说明(1.5):页面渲染与数据库操作[/b][/align][/size]
PandaJS 在服务器端的页面渲染也采用 JQuery Template 的语法,而数据库操作接口的语法与 Mongo Shell 上直接执行的原生语法非常接近。这使得我们可以在服务器端和客户端共用页面渲染代码,并且可以直接使用从前端传递过来的 JavaScript 对象与数据库进行交互。
不过,对客户端提交的数据进行校验也是必要的,这部分代码也可以在服务器端和客户端之间共用,这个话题将在后面的文章中提及,这里暂不考虑。
[size=medium][align=center][b]运行效果[/b][/align][/size]
代码编写完成后,可以启动应用并输入 [url]http://localhost[/url] 访问页面。
界面如下图所示:
[align=center][img]http://dl.iteye.com/upload/picture/pic/92014/4402a1f0-5403-316f-bc6a-342896c42bfc.png[/img][/align]
这个例子非常简单,只包括一个 users 列表,对每条数据可以执行两个操作:
1. 修改 description,并点击 Save 进行保存
2. 点击 Delete 删除对应的 user
最后一行用于增加 user ,可以执行两个操作:
1. 输入 name 和 description,并点击 Save 增加记录
2. 点击 Clear ,清空 name 和 description ,重新输入
第一次请求返回的是已经在服务器端渲染过的页面,而更新 users 列表后将会使用客户端 JS 重新渲染界面。
[size=medium][align=center][b]包含页面模板的 HTML[/b][/align][/size]
以下是包含模板的 html 文件,也就是后续需要进行操作的文件(webapp/index.html):
【content-tmpl】:包含表格的列标题和最后一行(用于增加 user )。
【user-tmpl】:表格的一行,包含 user 数据。
需要注意的是,内容为空的 div、table、script 元素中都增加了空格,这是因为服务器端渲染的序列化实际上使用的是 JAVA XML DOM 操作,会将这些内容为空的元素的起始标记和结束标记合并,如合并为 "<div />"、"<table />"、"<script />",这在浏览器中显示时会带来一些问题。
[size=medium][align=center][b]页面渲染代码[/b][/align][/size]
在 webapp/js/both/views.js 中,我们可以看到如下代码:
这里实现了 views.index(...) 方法,接收两个参数:jQuery 对象和 users 数据,然后完成对页面的渲染。
[size=medium][align=center][b]客户端 JS[/b][/align][/size]
客户端与该页面对应的 JS 文件是 webapp/js/index.js :
[size=medium][align=center][b]服务器端 JS (DBO)[/b][/align][/size]
数据库相关的服务器端 JS 位于 scripts/app/dbo/users.js。
它将根据获得的参数,对 MongoDB 的 conllection 进行操作 。
dbo.users 对象实现了以下四个方法:
1. list :获取 users 列表;当 users 为空时,进行数据初始化
2. add:创建 user,返回 users 列表
3. update:更新 user,以 name 作为查询条件,更新特定的 user ,返回 users 列表
4. remove : 删除 user,以 name 作为查询条件移除特定的 user ,返回 users 列表
[size=medium][align=center][b]服务器端 JS (PAGE)[/b][/align][/size]
页面请求默认映射到 page.index,url 格式为 [url]http://baseURL/method/params[0]/params[1]/...[/url]
baseURL 这里对应的是 localhost,method 这里对应的是 index (省略),method 之后的参数以 "/" 分隔,作为 params 数组;
也可以结合传统的传参方式: [url]http://baseURL/method/params[0]/params[1]?a=xxx&b=yyy[/url],
然后用 req,getParameter(...) 获取参数。
服务器端页面渲染 JS 位于 scripts/app/page/index.js:
通过 dbo.users.list() 获取 users 列表,并在 panda.render 的回调函数中调用公共的页面渲染函数进行界面渲染。
[size=medium][align=center][b]服务器端 JS (API)[/b][/align][/size]
供客户端 JS 调用的服务器端 API 的实现位于 scripts/app/api/users.js:
[size=medium][align=center][b]页面渲染 API 简述[/b][/align][/size]
页面渲染 API 的使用可以参考 scripts/test/lib/render 中的测试案例(通过 scripts/test/lib/render.js 运行) 。
首先,PandaJS 在服务器端提供对 jQuery 和 jQuery Template 语法的支持,但不包括 data 和 event。
请参考:
[url]http://api.jquery.com/[/url]
[url]http://api.jquery.com/category/plugins/templates/[/url]
这里不再重复说明。
下面以子页面的组装为例进行说明。
我们可以用两种方式实现页面组装:
方式一:
1.对子页面进行渲染,渲染目标为一个临时的 div,渲染完成后将 body 的内容替换为这个临时的div。
2.将子页面渲染结果插入到父页面中的指定位置
输入:
[b]test.html[/b]
[b]child.html[/b]
输出:
方式二:
1.将子页面的模板添加到父页面中
2.在父页面中统一进行渲染
输入:
test.html (同上,略)
[b]child-tmpl.html[/b]
输出:(同上,略)
[size=medium][align=center][b]数据库操作 API 简述[/b][/align][/size]
页面渲染 API 的使用可以参考 scripts/test/lib/db 中的测试案例(通过 scripts/test/lib/db.js 运行) 。
这里选择其中的几个重要的 API 进行介绍。
【通过 eval 进行数据的初始化】
数据初始化的脚本 before.js 调用 pure.db.eval(func) 进行数据的初始化,传入一个函数,函数中的代码将直接在 MongoDB 运行,可以在这里查看原生的 MongoDB js 的写法:
[url]http://www.mongodb.org/display/DOCS/SQL+to+Mongo+Mapping+Chart[/url]
因此,最好只在数据初始化、数据迁移等特殊应用场景使用它。
【list (find)】
list 是我根据自己的习惯为 MongoDB 原生 JS 中的 find 方法起的一个别名,仍然使用find也是可以的。
list 函数名前带上 “$” 表示将结果转换为 JavaScript 数组,否则将继续保留 DBCursor 形式,以便进行排序等后续操作。
【insert,update,remove】
使用 insert 插入数据,update 更新数据,remove 删除数据;也可以使用 save 插入或更新数据。
关于 upate 的更多用法,可以参考这里:
[url]http://www.mongodb.org/display/DOCS/Updating[/url]
PandaJS 在服务器端的页面渲染也采用 JQuery Template 的语法,而数据库操作接口的语法与 Mongo Shell 上直接执行的原生语法非常接近。这使得我们可以在服务器端和客户端共用页面渲染代码,并且可以直接使用从前端传递过来的 JavaScript 对象与数据库进行交互。
不过,对客户端提交的数据进行校验也是必要的,这部分代码也可以在服务器端和客户端之间共用,这个话题将在后面的文章中提及,这里暂不考虑。
[size=medium][align=center][b]运行效果[/b][/align][/size]
代码编写完成后,可以启动应用并输入 [url]http://localhost[/url] 访问页面。
界面如下图所示:
[align=center][img]http://dl.iteye.com/upload/picture/pic/92014/4402a1f0-5403-316f-bc6a-342896c42bfc.png[/img][/align]
这个例子非常简单,只包括一个 users 列表,对每条数据可以执行两个操作:
1. 修改 description,并点击 Save 进行保存
2. 点击 Delete 删除对应的 user
最后一行用于增加 user ,可以执行两个操作:
1. 输入 name 和 description,并点击 Save 增加记录
2. 点击 Clear ,清空 name 和 description ,重新输入
第一次请求返回的是已经在服务器端渲染过的页面,而更新 users 列表后将会使用客户端 JS 重新渲染界面。
[size=medium][align=center][b]包含页面模板的 HTML[/b][/align][/size]
以下是包含模板的 html 文件,也就是后续需要进行操作的文件(webapp/index.html):
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="css/index.css" rel="stylesheet" type="text/css" />
<title>DBO Example</title>
</head>
<body>
<div align="center">
<h1>DBO Example</h1>
<div id="error"> </div>
<table id="content"> </table>
</div>
<script id="content-tmpl" type="tmpl">
<tr align="left">
<th class="w100">User</th>
<th class="w150">Description</th>
<th class="w100" colspan="2">Operation</th>
</tr>
<tr align="left">
<td> <input class="w90"/> </td>
<td> <input class="w140" /> </td>
<td> <a href="##">Save</a> </td>
<td> <a href="##">Clear</a> </td>
</tr>
</script>
<script id="user-tmpl" type="tmpl">
<tr align="left">
<td> ${name} </td>
<td> <input class="w140" value="${desc}" /> </td>
<td> <a href="##">Save</a> </td>
<td> <a href="##">Delete</a> </td>
</tr>
</script>
<script src="js/lib/jquery.js"> </script>
<script src="js/lib/jquery.tmpl.js"> </script>
<script src="js/lib/json.js"> </script>
<script src="js/lib/panda.js"> </script>
<script src="js/both/views.js"> </script>
<script src="js/index.js"> </script>
</body>
</html>
【content-tmpl】:包含表格的列标题和最后一行(用于增加 user )。
【user-tmpl】:表格的一行,包含 user 数据。
需要注意的是,内容为空的 div、table、script 元素中都增加了空格,这是因为服务器端渲染的序列化实际上使用的是 JAVA XML DOM 操作,会将这些内容为空的元素的起始标记和结束标记合并,如合并为 "<div />"、"<table />"、"<script />",这在浏览器中显示时会带来一些问题。
[size=medium][align=center][b]页面渲染代码[/b][/align][/size]
在 webapp/js/both/views.js 中,我们可以看到如下代码:
// @include "../../../scripts/spket.js"
var views = {};
views.index = function($, users) {
var content = $("#content-tmpl").tmpl(),
rows = $("#user-tmpl").tmpl(users);
$("#content").html(content);
$("#content tr:first").after(rows);
$("#error").hide();
};
这里实现了 views.index(...) 方法,接收两个参数:jQuery 对象和 users 数据,然后完成对页面的渲染。
[size=medium][align=center][b]客户端 JS[/b][/align][/size]
客户端与该页面对应的 JS 文件是 webapp/js/index.js :
// @include "../../scripts/spket.js"
$(function(){
bind();
// 绑定事件
function bind() {
var rows = $("#content tr:not(:last) a"),
last= $("#content tr:last a");
rows.filter(":even").click(update);
rows.filter(":odd").click(remove);
last.eq(0).click(add);
last.eq(1).click(clear);
}
// 修改 user
function update() {
var $tr = $(this).parent().parent(),
name = $tr.find("td:first").html().trim(),
desc = $tr.find("input").val();
save("users.update", { name: name, desc: desc });
}
// 删除 user
function remove() {
var $tr = $(this).parent().parent(),
name = $tr.find("td:first").html().trim();
var req = { action: "users.remove", params: name };
panda.post(req, show);
}
// 增加 user
function add() {
var $input = $(this).parent().parent().find("input"),
name = $input.eq(0).val(),
desc = $input.eq(1).val();
save("users.add", { name: name, desc: desc });
}
// 清空输入
function clear() {
var $input = $(this).parent().parent().find("input");
$input.val("").eq(0).focus();
}
// 在修改或增加 user 时调用
// 发送请求到服务器端,返回后渲染页面
function save(action, user) {
var req = { action: action, params: user };
panda.post(req, show);
}
// 每次修改数据时重新渲染页面、绑定事件
function show(users) {
views.index($, users);
bind();
}
});
[size=medium][align=center][b]服务器端 JS (DBO)[/b][/align][/size]
数据库相关的服务器端 JS 位于 scripts/app/dbo/users.js。
它将根据获得的参数,对 MongoDB 的 conllection 进行操作 。
dbo.users 对象实现了以下四个方法:
1. list :获取 users 列表;当 users 为空时,进行数据初始化
2. add:创建 user,返回 users 列表
3. update:更新 user,以 name 作为查询条件,更新特定的 user ,返回 users 列表
4. remove : 删除 user,以 name 作为查询条件移除特定的 user ,返回 users 列表
// @include "../../../scripts/spket.js"
dbo.users = function(){
var users = panda.db.get("users");
return {
list: function() {
if (!users.count()) { init(); }
return users.list().$sort({ name: 1 });
},
add: function(user) {
users.insert(user);
return this.list();
},
update: function(user) {
var q = { name: user.name };
users.update(q, user, true, false);
return this.list();
},
remove: function(name) {
users.remove({ name: name });
return this.list();
}
};
function init() {
panda.db.eval(function(){
var users = db.users;
users.drop();
users.ensureIndex({ name: 1 }, { unique: true });
users.insert({ name: "user1", desc: "desc1" });
users.insert({ name: "user2", desc: "desc2" });
users.insert({ name: "user3", desc: "desc3" });
});
}
}();
[size=medium][align=center][b]服务器端 JS (PAGE)[/b][/align][/size]
页面请求默认映射到 page.index,url 格式为 [url]http://baseURL/method/params[0]/params[1]/...[/url]
baseURL 这里对应的是 localhost,method 这里对应的是 index (省略),method 之后的参数以 "/" 分隔,作为 params 数组;
也可以结合传统的传参方式: [url]http://baseURL/method/params[0]/params[1]?a=xxx&b=yyy[/url],
然后用 req,getParameter(...) 获取参数。
服务器端页面渲染 JS 位于 scripts/app/page/index.js:
// @include "../../../scripts/spket.js"
page.index = function(params, req, res) {
return panda.render("index", function($){
views.index($, dbo.users.list());
});
};
通过 dbo.users.list() 获取 users 列表,并在 panda.render 的回调函数中调用公共的页面渲染函数进行界面渲染。
[size=medium][align=center][b]服务器端 JS (API)[/b][/align][/size]
供客户端 JS 调用的服务器端 API 的实现位于 scripts/app/api/users.js:
// @include "../../../scripts/spket.js"
api.users = {
add: function(params, req, res) {
return dbo.users.add(params);
},
update: function(params, req, res) {
return dbo.users.update(params);
},
remove: function(params, req, res) {
return dbo.users.remove(params);
}
};
[size=medium][align=center][b]页面渲染 API 简述[/b][/align][/size]
页面渲染 API 的使用可以参考 scripts/test/lib/render 中的测试案例(通过 scripts/test/lib/render.js 运行) 。
首先,PandaJS 在服务器端提供对 jQuery 和 jQuery Template 语法的支持,但不包括 data 和 event。
请参考:
[url]http://api.jquery.com/[/url]
[url]http://api.jquery.com/category/plugins/templates/[/url]
这里不再重复说明。
下面以子页面的组装为例进行说明。
我们可以用两种方式实现页面组装:
方式一:
1.对子页面进行渲染,渲染目标为一个临时的 div,渲染完成后将 body 的内容替换为这个临时的div。
2.将子页面渲染结果插入到父页面中的指定位置
var htmlData = { htmlBody: "<b>HTML</b>" },
textData = { textBody: "Text" };
// 第一个参数为 true 表示渲染为子页面,不加入 doctype 等
var childHtml = pure.render(true, "child", function($) {
var $html = $("#child-html-tmpl" ).tmpl(htmlData),
$text = $("#child-text-tmpl" ).tmpl(textData)
var $target = $("#target").append($html).append($text);
$("body").html($target.html());
});
return pure.render("test", function($) {
$("#b").append(childHtml);
});
输入:
[b]test.html[/b]
<div id="a" name="pre-middle-aend" words="word xxx">
<div id="b" name="pre" words="xxxx word" class="clz">
<h1 id="h1">John</h1>
</div>
</div>
[b]child.html[/b]
<div id="target"></div>
<script id="child-html-tmpl" type="text/x-jquery-tmpl">
<p>{{html htmlBody}}</p>
</script>
<script id="child-text-tmpl" type="text/x-jquery-tmpl">
<p><b>${textBody}</b></p>
</script>
输出:
<DIV id="a" name="pre-middle-aend" words="word xxx">
<DIV class="clz" id="b" name="pre" words="xxxx word">
<H1 id="h1">John</H1>
<P><B>HTML</B></P>
<P><B>Text</B></P>
</DIV>
</DIV>
方式二:
1.将子页面的模板添加到父页面中
2.在父页面中统一进行渲染
var htmlData = { htmlBody: "<b>HTML</b>" },
textData = { textBody: "Text" };
var childTmpl = pure.render(true, "child-tmpl");
context.html = pure.render("test", function($) {
$("body").append(childTmpl);
var $html = $("#child-html-tmpl" ).tmpl(htmlData),
$text = $("#child-text-tmpl" ).tmpl(textData)
$("#b").append($html).append($text);
});
输入:
test.html (同上,略)
[b]child-tmpl.html[/b]
<script id="child-html-tmpl" type="text/x-jquery-tmpl">
<p>{{html htmlBody}}</p>
</script>
<script id="child-text-tmpl" type="text/x-jquery-tmpl">
<p><b>${textBody}</b></p>
</script>
输出:(同上,略)
[size=medium][align=center][b]数据库操作 API 简述[/b][/align][/size]
页面渲染 API 的使用可以参考 scripts/test/lib/db 中的测试案例(通过 scripts/test/lib/db.js 运行) 。
这里选择其中的几个重要的 API 进行介绍。
【通过 eval 进行数据的初始化】
数据初始化的脚本 before.js 调用 pure.db.eval(func) 进行数据的初始化,传入一个函数,函数中的代码将直接在 MongoDB 运行,可以在这里查看原生的 MongoDB js 的写法:
[url]http://www.mongodb.org/display/DOCS/SQL+to+Mongo+Mapping+Chart[/url]
pure.db.eval(function(){
var users = db.users;
users.ensureIndex({ name: 1 });
users.insert({ name: "user1", desc: "desc1" });
users.insert({ name: "user2", desc: "desc2" });
users.insert({ name: "user3", desc: "desc3" });
});
虽然 eval 很酷,但根据官方文档说明,运行 eval 时将阻塞所有的其他操作。
因此,最好只在数据初始化、数据迁移等特殊应用场景使用它。
【list (find)】
list 是我根据自己的习惯为 MongoDB 原生 JS 中的 find 方法起的一个别名,仍然使用find也是可以的。
list 函数名前带上 “$” 表示将结果转换为 JavaScript 数组,否则将继续保留 DBCursor 形式,以便进行排序等后续操作。
// 通过 pure.db.get 获取名为 "users" 的 collection
var users = pure.db.get("users"),
// 获取所有 user
listAll = users.$list(),
// 获取第 2-4 个 user
listWithLimit = users.list().skip(1).$limit(2),
// 获取 name 大于 "user1",小于 "user3" (按字典序)的 user
listWithCompare = users.$list({ name:{ $gt:"user1", $lt:"user3" } }),
// 获取 name 等于 "user1",或者 desc 等于 "desc2" 的 user
listWithOr = users.$list({ $or:[{ name:"user1"}, {desc:"desc2"} ] }),
// 获取所有 user,并按 desc 升序排列
listWithOrder = users.list().$sort({ desc: 1 }),
// 获取所有 user,并按 desc 排除重复,保持 desc 唯一性
listWithDistinct = users.distinct("desc"),
// 只获取 desc 字段,并且按 desc 升序排列
// 第一个参数,也就是条件参数为空, 表示不进行过滤,获取所有记录
listSomeField = users.$list({}, { desc:1 }),
// 用正则表达式进行获取,获取 name 中包含 "user" 的 user
listWithLike = users.$list({ name: /user/ }),
// 获取存在 "age" 属性的 user
listWithExists = users.$list({ age: { $exists: true } });
【insert,update,remove】
使用 insert 插入数据,update 更新数据,remove 删除数据;也可以使用 save 插入或更新数据。
// 插入 name 为 "user4", desc 为 "desc4" 的 user
users.insert({ name: "user4", desc: "desc4" });
// 更新 name 为 "user1" 的 user
var query = { name: "user1" };
users.update(query, { $set: { desc: "new desc" } });
// 更新 name 包含 "user" 的所有 user
// 第三个参数表示是否在记录不存在的情况下插入一条记录
// 第四个参数表示批量更新
var query = { name: /user/ };
users.update(query, { $set: { desc: "new desc" } }, false, true);
// 删除 name 为 "user1" 的 user
users.remove({ name: "user1" });
关于 upate 的更多用法,可以参考这里:
[url]http://www.mongodb.org/display/DOCS/Updating[/url]