13.7 应用程序的代码
本章演示的案例比先前的要复杂得多。这部分详述了使这个应用程序的主要代码(与本章中之前讨论的主题相关)。
以下是这个wiki应用程序运行必需的所有文件的列表包括了本章所讨论过的客户端和服务器端代码,以及所有没有直接提及的不同库和样式文件的混合。
l index.html:应用程序的主页,把所有的客户端代码结合到一起。
l install.html:安装的主文件,在第一次使用此应用程序时运行。
l css/style.css:样式化应用程序的客户端的CSS。
l
js/wiki.js(见代码清单13-9):JavaScript的主要代码,负责绑定事件和运行SQL查询。
l js/sql.js(见代码清单13-10):负责通信服务器,从SQL查询中获取数据。
l js/textile.js:一个JavaScript Textile库的副本(为把文本转换成HTML):http://jrm.cc/extras/ live-textile-preview.php。
l js/jquery.js:一个jQuery当前发行版本的副本:http://jquery.com/。
l api/:服务器端的主要代码,负责转换SQL查询结果为JSON和返回到客户端中。这个目录下包含Perl、PHP、Python和Ruby各自版本的代码。在此包含的是代码清单13-11所示的Ruby版本。
l data/wiki.db:保存wiki的SQLite数据库。
本章文件的完整代码见以下内容。
13.7.1 核心JavaScript代码
代码清单13-9展示了wiki.js,这是主要的JavaScript代码,负责绑定事件和与用户的交互。
代码清单13-9 js/wiki.js
// 获取当前页面的名称
var $s = window.location.search;
$s = $s.substr(1,$s.length);
// 确定是否提供修订号——如果有,记录它的ID
var $r = false;
// 修订是这样的格式 ?Title&RevisionID
var tmp = $s.split("&");
if ( tmp.length > 1 ) {
$s = tmp[0];
$r = tmp[1];
}
// 不提供页面的话,跳转到主页
if (!$s) window.location = "?HomePage";
// 设置数据库名字
var db = "wiki";
// 我们需要等待DOM的加载完毕
$(document).ready(function(){
// 设置页面的标题
document.title = $s;
$("h1").html($s);
// 载入所有的wiki修订版本
reload();
// 如果点击了“编辑页面”的连接
$("#edit").click(showForm);
// 当用户提交新的修订
$("#post form").submit(function(){
// 获取作者名字
var author = $("#author").val();
// 获取内容文本
var text = $("#text").val();
// 重新生成内容
$("#content").html(textile(text));
// 生成当前修订的时间(有助于高亮)
$r = (new Date()).getTime();
// 把修订插入到数据库中
sqlExec("insert,
[$s,author,text,$r], reload);
return false;
});
// 如果用户在编辑区域点击“取消”连接
$("#cancel").click(showContent);
});
// 显示当前修订
function showContent() {
// 显示“编辑”连接
$("#edit,#cancel").css("display","inline");
// 隐藏编辑区域
$("#post").hide();
// 显示内容
$("#content").show();
return false;
}
// 显示编辑当前修订的标单
function showForm() {
// 隐藏“编辑”连接
$("#edit").hide();
// 显示编辑区域
$("#post").show();
// 隐藏内容
$("#content").hide();
return false;
}
// 从数据库加载所有的修订版本
function reload(t) {
// 请求所有的修订版本
sqlExec("select, [$s],
function(sql) {
// 如果wiki存在修订
if ( sql.length > 0 ) {
if ( !$r ) $r = sql[0].date;
// 显示wiki页面
showContent();
// 显示所有的修订
$("#side ul").html('');
// 遍历所有的修订
for ( var i = 0; i < sql.length; i++ ) {
// 如果修订正是当前显示的
if ( sql[i].date == $r ) {
// 生成修订
$("#content").html(textile(sql[i].content));
// 让修订的内容可编辑
$("textarea").val( sql[i].content );
}
// 获取可工作的日期对象
var d = new Date( parsent sql[i].date);
// 判断修订是否在一天之内
if ( d.getTime() > (new Date()).getTime() - (3600 * 24000) )
// 如果是,生成漂亮的 am/pm 时间格式
d = d.getHours() >= 12 ?
(d.getHours() != 12 ? d.getHours() - 12 : 12 ) + " pm" :
d.getHours() + " am";
// 否则,直接显示修订的月日
else {
var a = d.toUTCString().split(" ");
d = a[2] + " " + d.getDate();
}
// 把修订添加到修订列表中
$("#side ul").append("<li class='" + ( $r == sql[i].date ? "cur" : "" )
+ "'><a href='?" + $s + ( i > 0 ? "&" + sql[i].date : "" )
+ "'>" + d + "</a> by " + sql[i].author + "</li>");
}
// 否则,这个页面未经修订过
} else {
// 把这个事实显示到修订面板上
$("#rev").html("<li>No Revisions.</li>");
// 隐藏编辑控制
$("#edit,#cancel").hide();
// 显示默认的编辑表单
showForm();
}
});
}
13.7.2 JavaScript SQL库
代码清单13-10展示了sql.js,这些代码负责通信服务器并从SQL查询中获取JSON数据。
代码清单13-10 js/sql.js
// 根据你的服务器端脚本的所在更新这个变量
var apiURL = "api/ruby/";
// 一些默认的全局变量
var
sqlLoaded = function(){}
// 处理长的SQL提交
// 这个函数可用以发送大量的数据(比如,大的INSERT),但只能发送到服务器上与客户端相同的位置
function sqlExec(q, p, callback) {
// 加载所有参数到结构化数组中
for ( var i = 0; i < p.length; i++ ) {
p[i] = { name: "arg", value: p[i] };
}
// 包含数据库名
p.push({ name: "db", value: db });
// 执行SQL查询名
p.push({ name: "sql", value: q });
// 提交查询到服务器
$.ajax({
// POST to the API URL
type: "POST",
url: apiURL,
// 数组数据序列化
data: $.param(p),
// 返回JSON数据
dataType: "json",
// 等待成功完成响应
// 如果用户指定了反馈,返回数据
success: callback
});
}
13.7.3 Ruby服务器端代码
以下代码清单13-11中的代码,是Ajax wiki应用程序的服务器端部分,所有这些代码是以Ruby编程语言编写的。而以PHP、Perl或者Python编写的相同代码,可以访问本书的网站http://jspro.org/得到。
代码清单13-11 Wiki应用程序的服务器端部分代码,以Ruby编写
#!/usr/bin/env ruby
# 引入所有的外部库
require 'cgi'
require 'rubygems'
require_gem 'sqlite3-ruby'
require 'json/objects'
# 输出JavaScript头
print "Content-type: text/javascript\n\n"
# 初始化应用程序变量
err = ""
r = []
cgi = CGI.new
# 捕获由用户传入的值
call = cgi['callback']
sql = cgi['sql']
# 从用户处获取数据库并确保没有使用攻击性的字符
d = cgi['db'].gsub(/[^a-zA-Z0-9_-]/, "")
# 如果不提供数据库,则使用默认的'test'
if d == '' then
d = "test"
end
# 获取放入SQL查询中的参数列表
args = cgi.params['arg']
# 只接受两种不同SQL查询
# 在数据库中插入wiki新修订版
if sql == "insert" then
sql = "INSERT INTO wiki VALUES(?,?,?,?);"
# 获取所有的wiki修订版
elsif sql == "select" then
sql = "SELECT * FROM wiki WHERE title=? ORDER BY date
DESC;"
# 否则,查询失败
else
sql = ""
end
# 如果提供了SQL查询
if sql != '' then
# 遍历每个提供的参数
for i in 0 .. args.length-1
# 代替所有''的引用(相当于在SQLite中避开它们),避开所有?
args[i] = args[i].gsub(/'/, "''").gsub(/\?/, "\\?")
# 遍历SQL查询替代第一个符合条件的数据
sql = sql.sub(/([^\\])\?/, "\\1'" + args[i] + "'")
end
# 完成后,un-escape标志所避开的东西
sql = sql.gsub(/\\\?/, "?")
# 确保我们能捕获所有抛出的错误
begin
# 连接到SQLite数据库,它只是一个文件而已
db = SQLite3::Database.new('../../data/' + d + '.db')
# 如果sql返回部分行(比如,它是一条SELECT)
db.query( sql ) do |rows|
# 遍历所有返回的行
rows.each do |row|
# 创建一个临时散列
tmp = {}
# 强制数组栏目转换为散列的 键/值对
for i in 0 .. rows.columns.length-1
tmp[rows.columns[i]] = row[i]
end
# 把行三列添加到已找到的行数组中去
r.push tmp
end
end
rescue Exception => e
# 如果出错,记录信息备用
err = e
end
else
# 如果没有提供SQL查询,显示一条错误
err = "No query provided."
end
# 如果出错,则返回一个散列,它包含错误信息的键和值对
if err != '' then
r = { "error" => err }
end
# 把返回的对象转换成JSON字符串
jout = r.to_json
# 如果提供了回调函数
if call != '' then
# 在回调字符串中包裹返回的对象
print call + "(" + jout + ")"
else
# 否则直接输出JSON字符串
print jout
end






