您正在构建 Web 应用程序吗?这个应用程序是像 cragislist,还是更像 flickr?如果答案是前者,那么您可以跳过本文了。如果是后者,本文正适合您。在这个包含三部分的系列文章的第
2 部分中,学习如何用 Scriptaculous JavaScript 库增强 Web 应用程序。
本文是包含三部分的系列文章的第 2 部分,这个系列讨论可以用来创建 Ajax 应用程序的流行的 JavaScript 库。在
第 1 部分 中,学习了如何用 Prototype 库创建一个用来管理歌曲的 Web 应用程序。在本文中,将使用 Scriptaculous
库构建一个用来管理照片的 Web 应用程序。
本文使用的是 Scriptaculous 的最新版本 1.8.1(参见 参考资料
中的链接)。Scriptaculous 使用 Prototype 1.6 库。您必须熟悉 JavaScript、HTML 和 CSS。本文演示
Scriptaculous 在 Ajax 方面的应用。在后端,结合使用 Ruby on Rails 2.0 和 MySQL 5.0.4(参见
参考资料)。只需稍微调整,就可以改用其他后端技术。
Scriptaculous JavaScript 库是目前最流行的库之一。它用来在基于 HTML 的 Web 站点中添加丰富的交互功能。它提供了许多视觉效果和行为,帮助开发人员在
Web 应用程序中添加交互功能。Scriptaculous 是在 Prototype 库的基础上构建的。
图 1. Scriptaculous 和 Prototype 的关系
如果您阅读了第 1 部分,应该已经见过 Prototype 提供的 Ajax 抽象示例。Scriptaculous 并不自行创建相似的功能,而是使用
Prototype 并在其上添加效果和行为。Scriptaculous 提供拖放元素等大量控件。还提供可以与控件结合使用的非常出色的视觉效果。
最有用、视觉上最吸引人的 Scriptaculous 特性之一是拖放。拖放特性在桌面应用程序中非常常见,但是在 Web 应用程序不常见。在
Web 应用程序中添加这种特性可以提供丰富的用户体验。这个任务看起来很困难,但是 Scriptaculous 大大简化了它。为了演示这个特性,我们将构建一个示例应用程序,通过分析它了解使用
Scriptaculous 的好处。
示例应用程序:照片管理器
我们将创建一个用来管理照片的应用程序作为示例应用程序。我们的应用程序允许通过拖放操作在照片集中添加或删除照片。在后端,我们将使用
Ruby on Rails 和 MySQL。Ruby on Rails 实际上默认包含 Scriptaculous 并提供 API,可以在应用程序中自动使用
Scriptaculous。我们并不从 Rails 使用这些特性;我们只使用 Rails 提供数据服务后端。
照片管理器后端
我们不打算花费太多时间讨论后端,对后端的认识只需足以理解前端操作的意义就够了。本文的 下载
部分提供所有代码。首先,我们使用 rails pix 创建一个 Rails 应用程序。接下来,需要按照 Rails 命名约定创建一个称为
pix_development 的数据库。需要修改 config/database.yml 文件,根据这个数据库修改配置参数(主机、名称、密码)。
下一步是最重要的步骤:为应用程序创建 scaffold。在一般情况下,创建 scaffold 仅仅是开发的基础;但是,在这个示例应用程序中,我们将用它创建一个
REST 式的接口,可以使用 Ajax 调用这个接口。命令如下:ruby script/generate scaffold
photo thumb :string caption :string inSet :boolean 。这会创建一个模型,其中包含一个
thumb 字段(用于缩略图)、一个 caption 和一个 Boolean(表示照片是否在照片集中)。注意,命令
rake db:migrate 将在数据库中创建我们需要的表。scaffold 生成命令还为应用程序创建一个控制器。我们需要修改这个控制器,为
Rails 自动创建的 REST 式服务提供 JSON 版本。修改后的控制器见清单 1:
清单 1. 照片 Web 控制器
class PhotosController < ApplicationController
# GET /photos
# GET /photos.xml
protect_from_forgery :only => [:create]
def index
@photos = Photo.find(:all)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @photos }
format.json { render :json => @photos }
end
end
# GET /photos/1
# GET /photos/1.xml
def show
@photo = Photo.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @photo }
format.json { render :json => @photo }
end
end
# GET /photos/new
# GET /photos/new.xml
def new
@photo = Photo.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @photo }
format.json { render :json => @photo }
end
end
# GET /photos/1/edit
def edit
@photo = Photo.find(params[:id])
end
# POST /photos
# POST /photos.xml
def create
@photo = Photo.new(params[:photo])
respond_to do |format|
if @photo.save
flash[:notice] = 'Photo was successfully created.'
format.html { redirect_to(@photo) }
format.xml { render :xml => @photo, :status => :created,
:location => @photo }
format.json { render :json => @photo, :status => :created,
:location => @photo }
else
format.html { render :action => "new" }
format.xml { render :xml => @photo.errors, :status =>
:unprocessable_entity }
format.json { render :json => @photo.errors, :status =>
:unprocessable_entity }
end
end
end
# PUT /photos/1
# PUT /photos/1.xml
def update
@photo = Photo.find(params[:id])
respond_to do |format|
if @photo.update_attributes(params[:photo])
flash[:notice] = 'Photo was successfully updated.'
format.html { redirect_to(@photo) }
format.xml { head :ok }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @photo.errors, :status =>
:unprocessable_entity }
format.json { render :json => @photo.errors, :status =>
:unprocessable_entity }
end
end
end
# DELETE /photos/1
# DELETE /photos/1.xml
def destroy
@photo = Photo.find(params[:id])
@photo.destroy
respond_to do |format|
format.html { redirect_to(photos_url) }
format.xml { head :ok }
format.json { head :ok }
end
end
end |
请注意这里的几个元素。首先,protect_from_forgery 命令允许以 Web 服务的形式访问所有操作,但是创建操作除外。接下来,注意
respond_to do |format| 块,我们在其中添加了 format.json
{ render :json ... } 。这会用 RoR 内置的 JSON 支持响应以 .json 结尾的 URL。所以,/photos.json
将用序列化为 JSON 的数据调用索引方法。正如在本系列的
第一篇文章 中看到的,这是一种向 Prototype 传递数据的非常方便的方法。我们已经看到了后端上的数据形式以及提供数据的方式。现在看看前端以及如何在前端使用
Scriptaculous。
照片管理器前端
为了理解前端,我们来看看代码并研究它的工作方式。这个前端是一个简单的 HTML 页面,见清单 2:
清单 2. organize.html Web 页面
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Photo Organizer</title>
<script type="text/javascript" src="javascripts/prototype.js"></script>
<script type="text/javascript" src="javascripts/effects.js"></script>
<script type="text/javascript" src="javascripts/dragdrop.js"></script>
<script type="text/javascript" src="javascripts/builder.js"></script>
<script type="text/javascript" src="javascripts/organizer.js"></script>
<script type="text/javascript" src="javascripts/organize_page.js"></script>
<link rel="stylesheet" href="stylesheets/organizer.css"/>
</head>
<body onLoad="initPage()">
<div id="pageTitle">Organize Your Photos</div>
<div id="allPics">
<span id="setPics"></span>
<span id="availablePics"></span>
</div>
<div id="trash"> </div>
</body>
</html> |
这看起来非常简单,对吗?注意,我们装载了几个 JavaScript 文件。因为 Scriptaculous 要使用 Prototype(我们自己也要使用它),所以必须装载
prototype.js。然后,使用属于 Scriptaculous 的三个库:effects.js、dragdrop.js 和
builder.js。Scriptaculous 是模块化的,所以可以只使用需要的特性。但是,dragdrop.js 依赖于 effects.js,所以只要使用拖放特性,就必须同时包含这两个文件。当然,在这两个
JavaScript 文件中,我们最感兴趣的代码是与这个应用程序相关的代码。当装载页面时,它调用 organize_page.js
中的 initPage 函数,见清单 3:
清单 3. 页面初始化 JavaScript
// the mode for the page
var model = {};
// page initialization
function initPage(){
var options = {
method : "get",
onSuccess : initUi
}
new Ajax.Request("photos.json",options);
}
function initUi(xhr){
model = new Organizer(xhr.responseJSON);
var list = Builder.node("span", {id: "inList"});
var list2 = Builder.node("span", {id: "outList"});
var pix = model.pixList;
for (var i=0; i < pix.length; i++){
var pic = buildPicUi(pix[i]);
if (pix[i].inSet){
list.appendChild(pic);
} else {
list2.appendChild(pic);
}
}
$("setPics").appendChild(list);
$("availablePics").appendChild(list2);
// setup drag-and-drop
makeDraggables();
initDropZones();
} |
如果您以前使用过 Prototype,应该很熟悉第一个函数 initPage 了。它仅仅向服务器请求一个
JSON 形式的照片列表。它把 initUi 函数设置为回调函数,这个函数处理服务器返回的响应。下面看看这个函数。
initUi 函数获取服务器返回的数据,并创建一个 Organizer 对象。这个对象包含在一个单独的
JavaScript 文件中,作为应用程序的模型。您可以看看它的代码(见本文的 源代码)。目前,我们主要关注
UI 组件。我们为两组照片动态地创建视觉组件:照片集中的一组照片,照片集之外的一组照片。我们使用 Scriptaculous 的
Builder 库动态地为它们创建 DOM 元素。接下来,循环遍历模型中的照片,使用 buildPicUi
函数为它们创建视觉表示。这个函数见清单 4:
清单 4. buildPicUi JavaScript 函数
function buildPicUi(pic){
var picId = "pic" + pic.id;
var img = Builder.node("img", {src : "images/"+pic.thumb, alt: pic.caption});
var picNode = Builder.node("div",{id : picId, class: "pic"},img);
return picNode;
} |
再次使用 Builder 库创建 DOM 元素。在这里,我们在一个 div 中创建一个图像(img 标记)。现在,如果再看看
initUi 函数,会看到对另外两个函数的调用:makeDraggables
和 initDropZones 。这些函数见清单 5:
清单 5. 启用拖放
function makeDraggables(){
for (var picId in model.pixMap){
new Draggable(picId, {revert:true});
}
}
function initDropZones(){
Droppables.add("setPics",{onDrop: addToSet, accept: "pic"});
Droppables.add("availablePics",{onDrop: removeFromSet, accept: "pic"});
Droppables.add("trash",{onDrop: addToTrash, accept: "pic"});
} |
这两个函数在应用程序中启用所有拖放功能。makeDraggables 函数循环遍历所有照片并为每张照片创建一个
Draggable 对象。这个函数只需要照片的 DOM ID(见清单 4 中的 buildPicUi
函数)和选项。在这里,惟一的选项是 revert = true。这表示,除非照片位于放置区,否则应该把它放回原来的位置。放置区由
initDropZones 创建。
initDropZones 获取页面上的三个区域:in-list、out-list 和底部的 “垃圾”
区。对于每个区域,它设置一个处理函数(onDrop ),当对象被拖放到放置区时,会调用相应的函数。它还设置一个过滤器,用来控制可以拖放到这个区域的对象种类。在这里,所有放置区只接受
“pic” 类的对象。这个类是在清单 4 中的 buildPicUi 函数中为每个照片设置的。因此,只能把照片拖放到放置区中,而且照片只能拖放到接受它们的放置区中。每个区有不同的处理函数。我们来看看
adToTrash 处理函数,见清单 6:
清单 6. addToTrash JavaScript 函数
function addToTrash(pic){
pic.style.left = "";
pic.style.top = "";
$("trash").appendChild(pic);
Effect.Puff(pic.id, {duration: 0.8});
model.deletePic(pic.id);
} |
这个函数做几件事。首先,它清除在拖动对象时创建的一些 CSS。然后,把这个照片添加到 “垃圾” 区中。注意这里如何使用 Prototype
的 $("trash") 表示法。接下来,使用一种称为 Puff 的 Scriptaculous
效果。这种视觉效果显示照片消失的过程,表示已经删除它。Scriptaculous 把这样的效果称为组合效果:它使用库的核心效果的组合(也可以使用许多其他效果)。最后,通过调用模型(Organizer
对象)发出 Ajax 调用,从数据库中删除这个对象。Organizer 对象见清单 7:
清单 7. Organizer 类
var Organizer = Class.create({
initialize : function(pix){
this.pixMap = {};
this.pixList = pix;
pix.each(function(pic){
this.pixMap["pic"+pic.id] = pic;
}.bind(this));
},
updatePic : function(picId, data){
var photo = this.pixMap[picId];
params = [];
// Rails uses _method since browsers
// do not properly send HTTP PUT and DELETE
params["_method"] = "put";
// Rails uses className[propertyName] for
// request parameters to auto-bind them to
// object properties
$H(data).each(function(pair){
params["photo["+pair.key+"]"] = pair.value;
});
var options = {
method : "post",
parameters : params
};
new Ajax.Request("photos/"+photo.id,options);
},
deletePic : function(picId){
var photo = this.pixMap[picId];
var params = {};
params["_method"] = "delete";
var options = {
method : "post",
parameters : params
};
new Ajax.Request("photos/"+photo.id,options);
}
}); |
这个类有许多有意思的地方。与 Scriptaculous 一样,它大量使用了 Prototype。它使用 Prototype
创建一个类(Class.create(...) )。请看一下 initialize
函数。在创建 Organizer 实例时会调用这个函数。它使用 Prototype 在 JavaScript 数组和对象中添加的
each 函数。在许多编程语言中都有相似的构造,但是在 JavaScript 中没有。在我们的示例中,它与
Prototype 的 bind 函数结合使用。这个函数是 Prototype 在 JavaScript
的 function object 中添加的。它会把当前上下文绑定到这个函数。在这里,在执行 each
函数调用的匿名函数期间,我们希望能够修改 Organizer 的 pixMap 字段。如果不进行绑定,this.pixMap 就没有意义。另外,updatePic
函数使用另一个 Prototype 语法简写方式,即 $H() 。这会创建一个 Prototype 散列对象,这个对象在我们的对象中添加
each 函数等。这也是 Prototype 在 JavaScript 中添加的从其他语言借鉴的构造。最后,updatePic
和 deletePic 使用 Prototype 的 Ajax.Request
函数创建一个跨浏览器兼容的 Ajax 服务器请求。
为了运行这个示例,需要启动 Rails:ruby script/server start。现在,可以在浏览器中打开这个示例,见图
2:
图 2. 照片管理器示例
在装载这个页面时,可以使用 Firebug 等工具观察异步装载数据的过程,见图 3:
图 3. Firebug 中显示的初始数据装载过程
在把照片从一个列表拖放到另一个列表或屏幕底部的 “垃圾” 区时,应该会看到另一个 Ajax 请求,见图 4:
图 4. 拖放所触发的 Ajax 请求
现在,我们的 Web 应用程序具备了许多与桌面应用程序相似的功能。它是使用 Scriptaculous 提供的高级控件和效果构建的,并利用
Prototype 简化了 Ajax 开发。
本文是讨论 JavaScript 库的
包含三部分的系列文章 的第 2 部分。在本文中,我们研究了 Scriptaculous JavaScript 库提供的一些控件和效果。了解
Scriptaculous 是如何在 Prototype 的基础上构建出来的,以及它如何帮助简化 Ajax 开发。我们还看到如何在
Prototype 的 Ajax 中结合使用 Scriptaculous 控件和效果,创建丰富的用户体验。但是,我们只涉及到了
Scriptaculous 的皮毛。Scriptaculous 还有可以在 Web 应用程序中使用的其他控件和效果,值得您花时间进一步研究。
描述 |
名字 |
大小 |
下载方法 |
示例代码 |
wa-aj-ajaxpro2.zip |
170KB |
|
学习
获得产品和技术
讨论 |