使用Ajax技术
介绍
Ajax对于Web2.0来说可以说是里程碑式的技术,Ajax是由一些列技术组成的可以让开发人员实现可交互式、功能丰富的Web应用程序。这些技术在Ajax出现之前已经存在很多年了,然而使用Ajax技术可以让你实现无属性的页面数据交互。
由于我们的项目是一个Web2.0应用程序,所以增加用户体验变得尤为重要。我们项目的成功取决于用户可以通过它方便的实现发布和共享书签的功能。本章将通过使用Ajax技术使用户界面更优化并提高交换性。
本章你将学到以下内容:
Ajax以及在Web应用程序中使用它的好处。
如何在Django中安装Ajax架构。
如何使用开源的jQuery架构。
增加标签搜索功能。
不加载新页面直接修改一个书签。
发布书签时,提供标签的自动补全功能。
Ajax以及使用它的好处
Ajax是Asynchronous JavaScript and XML的缩写,意思是异步JavaScript与XML,包括以下技术:
HTML和CSS用于构造页面和样式。
JavaScript用于动态的访问和操作页面信息。
XMLHttpRequest,这是一个有浏览器提供的对象,通过它可以在不刷新页面的情况下实现客户端与服务器之间的数据交互。
一种用于在客户端和服务器端交换数据的数据格式。XML是一种格式,也可以使用HTML,普通文本或者符合JavaScript标准的JSON格式。
使用Ajax时,如果用户发出一个请求无需刷新页面就可以在后台实现客户端到服务器之间数据交换。这样开发人员可以更好的实现页面交换行为并增加用户体验。
正确使用Ajax技术有如下好处:
更好的用户体验。使用Ajax用户可以在不刷新页面的情况下完成功能工作,就像操作一个桌面应用程序一样。
更好的性能。使用Ajax只有请求数据被发送到服务器端,节省可带宽资源提供了应用程序速度。
有很多应用程序使用了Ajax技术,Google的地图服务和Gmail就是很好的例子。实际上这两个应用是使用Ajax的杰出代表。Gmail与其他邮件服务的一个不同之处是他的用户界面,用户在使用Gmail操作是可以不用等待页面的重新加载直接在当前页面完成操作。这确实提高了Gmail的用户体验,使用Gmail时你会感觉你使用的是一个功能丰富、反映快速的应用程序而不再只是一个web站点。
本章介绍了如何在Django中使用Ajax技术,从而提升我们项目的用户体验。我们将实现当今Web应用程序中三种主要的Ajax应用。不过在这之前我们将先来看看使用Ajax框架而不是原生的JavaScript的好处。
在Django中使用Ajax框架
本节我们将选择并在我们的项目中安装一个Ajax框架。当然这不是在Django中使用Ajax所必须的,但是这将大大提高使用Ajax的便利性。下列了使用Ajax的好处:
在不同的浏览器之间JavaScript的实现是不同的。一些浏览器提供了功能完善的实现,而另一些的实现并不完全或者并不是标准的实现。如果不使用Ajax框架,开发人员就要注意不同浏览器中的这些区别,对于同一个功能可能要根据不同的浏览器单独实现。但是,如果使用Ajax框架这一切都由框架来负责,Ajax框架抽象了对不同浏览器JavaScript实现的访问方法。这样我们就可以专注于功能的实现,而不必再操心不同浏览器和它们的限制。
标准JavaScript提供的函数和类无法满足web应用程序的需求。我们不得不变量很多代码来完成大多数常用的功能。因此,即使你不打算使用Ajax框架,你也会发现你不得不编写一个函数库来封装这些JavaScript从而提供他们的重用性。但是既然这里已经有很多现成的优秀的开源框架我们为什么还要再开发一个呢?
目前市面上有很多Ajax框架,大到提供综合解决方案的框架:这些框架提供服务端到服务端、客户端到服务端的组件;小到轻量级的框架:这些框架只提供了客户端到服务器端JavaScript 库封装。根据目前我们项目的需要,一个客户端到服务端的Ajax框架就够了。另外,这个Ajax框架应该能够方便的集成到Django中,而且这个框架最好是轻量级的快速的框架。有很多优秀的框架都能满足我们的需求,比如Prototype,Yahoo! UI Library 和 jQuery。这些框架我都使用过,而且他们都很不错。但是对于当前的项目我准备使用jQuery,因为它是其中最轻量级的一个。而且jQuery有一个活跃的开发社区和大量的插件。如果你熟悉其他的Ajax框架你可以在本章中继续使用他们。不管你使用哪种Ajax框架服务端的Ajax代码都是一样的。
现在你已经了解了使用Ajax框架的好处,现在我们准备把jQuery安装到Django中。
下载并安装jQuery
使用jQuery的好处之一是它只有一个单独的文件,你可以在http://jquery.com上下载它的最新版本。你有两个选择:
未压缩版本:这是标准版本,我建议你在开发时使用这个版本,你将得到一个.js文件,所有的库函数代码都包含在其中。
压缩版本:你得到的还是一个.js文件,不过代码看起来有点混乱。jQuery的开发人员为了减少文件的尺寸,对未压缩版的文件做了一些改动,比如移除空格、重命名变量等等。当你将应用程序部署到生产环境时这个版本非常有用,因为它提供了和未压缩版本一样的功能,但是文件更小了。
我建议你使用未压缩版本,因为在开发阶段你可能想要查看jQuery中的一段代码到底是如何运行的。不过这两个版本提供的功能是一样的,从一个版本切换到另一个只要把文件替换一下就可以了。
下载了jquery-xxx.js(xxx是版本号)文件后,把它重命名为jquery.js,复制到我们项目的site_media文件夹下,接下来把这个文件包含到我们的base.html模板文件中,这样我们就可以在每个集成它页面中使用jQuery了。打开 templates/base.html 文件加入下面代码中黑体字部分:
<head>
<title>Django Bookmarks
{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="/site_media/style
type="text/css" />
<script type="text/javascript"
src="/site_media/jquery.js"></script>
</head>
你也可以把自己的JavaScript代码放在一个单独的.js文件中然后像这样把它包含到html页面中,或者可以向下面这样直接将代码嵌入到HTML中:
<script type="text/javascript">
// JavaScript code goes here.
</script>
建议使用第一种方法,因为把HTML和JavaScript分开使代码更加清晰。由于我们准备使用自己的.js文件,我们需要一种方法将.js文件连接到模板中而不用每次都更改base.html文件。我们来创建一个模板块,将它置于HTML的head标签之间。我们跟这个模板块取名为external,因为它将链接一个外部的文件到当前模板中,打开 templates/base.html 文件加入下面黑体字部分:
<head>
<title>Django Bookmarks {% block title %}{% endblock %}</title>
<link rel="stylesheet" href="/site_media/style.css"
type="text/css"/>
<script type="text/javascript" src="/site_media/jquery.js">
</script>
{% block external %}{% endblock %}
</head>
现在如果子模板中需要使用JavaScript代码,只要重载这个external就可以了。
在我们开始使用Ajax之前,先来大概的介绍一下jQuery框架。
jQuery JavaScript框架
jQuery是一个JavaScript函数库,它用于与HTML文档交换以及操作HTML文档。使用jQuery你可以大大的节省开发时间并且避免了跨浏览器带来的问题。
通常使用jQuery分以下两步:
选取一个或一组要操作的HTML元素。
使用jQuery提供的方法来操作这些HTML元素。
选取HTML元素
jQuery提供了一个非常简单的方法来选取HTML元素,你可以通过将HTML元素标签作为字符串参数传递给 $函数来实现元素的选取。下面举几个例子:
如果你想选择一个页面中所有的(<a>)元素,可以通过调用$("a") 函数来实现。
如果你想选取带有 .title class 属性的 <a> 元素,可以通过调用$("a.title")函数来实现。
如果你想选择一个ID属性为#nav的元素,可以通过调用$("#nav")来实现。
如果你想选择属性ID为#nav 元素下的所有(<li>) 元素,可以通过调用$("#nav li")来实现。
$()函数返回一个jQuery对象,一旦你得到这个对象就可以通过这个对象的方法来与HTML元素进行交互了。
jQuery方法
jQuery提供了大量的方法来操纵HTML文档。通过这些方法,你可以显示或者隐藏页面元素,给元素增加监听方法,修改元素的CSS属性,控制页面结构,更重要的是你可以通过jQuery执行Ajax请求。
在学习jQuery之前,我强烈建议你使用FireFox浏览器并安装它的一个插件FireBug,这对于开发和调试jQuery程序非常有帮助。这个扩展提供了一个非常类似于Python控制台的JavaScript控制台,通过这个插件你可以直接输入JavaScript语句并立即看到结果,你不必再单独的创建一个文件来查看输出结果。我们可以在 http://www.getfirebug.com/上下载安装Firebug插件。
如果由于某些原因你不想使用Firefox,Firebug为其他浏览器提供了一个“lite”版本的扩展,这个扩展是一个JavaScript文件。你可以下载这个文件并把它保存到site_media目录下,然后把这个文件包含到templates/base.html文件中:
<head>
<title>Django Bookmarks {% block title %}{% endblock %}</title>
<link rel="stylesheet" href="/site_media/style.css"
type="text/css"/>
<script type="text/javascript" src="/site_media/firebug.js">
</script>
<script type="text/javascript" src="/site_media/jquery.js">
</script>
{% block external %}{% endblock %}
</head>
现在我们来看看如果使用Firebug插件,运行开发服务器然后打开浏览器并按下F12键,试着操作一下。
隐藏和显示元素
让我们先从简单的开始,hide()表示隐藏元素,show()方法表示显示元素。比如下面的例子中我们通过命令来改变导航菜单的行为:
>>> $("#nav").hide()
>>> $("#nav").show()
通过jQuery我还可以在元素显示或者隐藏的时候个他加上一些特效,试试这些方法 fadeOut(), fadeIn(), slideUp() 或者 slideDown() ,你看到了什么?
这些方法和我们前面将的方法类似,比如你执行下面的命令页面上所有的标签都会消失:
>>> $('.tags').slideUp()
访问CSS属性和HTML元素属性
接下来我们学习如何修改页面元素的CSS属性。jQuery提供了一个方法名为css()来执行CSS操作。如果你调用这个方法并把CSS属性名作为参数传递给它,这个方法会返回CSS参数的值:
>>> $("#nav").css("display")
Result: "block"
如果你给这个方法传递第二个参数,那么这第二个参数会被当作第一个参数的值,在方法执行后会重新设置这个CSS属性的参数值:
>>> $("#nav").css("font-size", "0.8em")
Result: <div id="nav" style="font-size: 0.8em;">
实际上你可以向操作CSS一样操作HTML元素的任何属性。同样jQuery提供了一个名为attr()方法。如果你给它传入的是一个元素属性名,那么这个方法返回的是这个属性的值,如果你传入的是一对属性名称/值,就会重新设置这个属性的值。为了测试这个方法,我们打开书签提交表单,输入下面的内容:
>>> $("input").attr("size", "48")
Results:
<input id="id_url" type="text" size="48" name="url">
<input id="id_title" type="text" size="48" name="title">
<input id="id_tags" type="text" size="48" name="tags">
上面的方法将页面中所有的文本输入框的长度修改为48。
另外,jQuery还提供了一些快捷方法来完成通常的取值和赋值操作,比如val()方法返回一个文本输入框的值,如果这个方法有一个参数值,那么就会用参数值来设置文本输入框的值。还有两个方法addClass() 和removeClass()方法用于给HTML元素增加或删除CSS样式,toggleClass()方法用于触发HTML元素的CSS样式,也就是说使它立即生效。
操作HTML文档
现在你已经熟悉了如何控制页面内的元素属性,现在我们来看看如何在页面中增加或者移除元素。要在一个页面元素之前增加一段HTML代码调用方法before()方法,要在元素后面增加HTML代码调用after()方法。你看jQuery的方法名非常好记,不是吗?
现在让我们来测试一下这个两个方法,打开一个页面,输入下面的内容:
>>> $(".tags").before("<strong>(</strong>")
>>> $(".tags").after("<strong>)</strong>")
你可以通过这种方法在页面的任何位置增加你想要的内容,这些内容可以是HTML标记,也可以是任何文本内容,这些方法为操作HTML页面内容提供了非常灵活的方式。
如果你想在页面上移除内容,可以调用remove()方法,比如:
$("#nav").remove()
这个方法不仅是让元素在页面上消失,而是彻底的从页面上删除了元素。你没办法再次获取被移除的元素,比如:
>>> $("#nav")
Result: []
当然,你只是在当前的页面实例中移除了这个元素,它并没有真正的消失,如果你重新打开这个页面,被移除的元素会重新显示。
遍历文档树
尽管jQuery提供的选取器已经很强大了,你可以通过它找到你要的元素,但是有的时候我们希望从HTML文档的某个特殊部分遍历元素。为此,jQuery提供了一系列的方法来完成这些操作, parent() 方法返回元素的一个上层元素, children()方法返回当前元素的所有子元素。find()方法用于找到当前元素组中具体的某个或者某组元素,比如 $("#nav").find("li") 会找到,菜单组中使用的li元素。如果你想访问元素集合中某个特定的元素可以通过get加索引参数的方式,比如: $("li").get(0) 会得到第一个个元素。
控制事件
接下来我们讨论事件控制器。一个事件控制器就是一个JavaScript函数,它在某些特定事件下会被触发,比如单击一个按钮或者提交表单的时候。jQuery提供了大量的方法来维护时间操作,在我们当前的项目中我们只关心鼠标点击和表单提交两种事件。对于鼠标点击事件我们需要用到click()方法,现在我们在Firebug中输入下面的内容:
>>> $("p").after("<button id=\"test-button\">Click me!</button>")
(注意,这里我们必须使用\ 符号来对引号进行转义)
如果现在你点击这个按钮,什么都不会发生。那么,让我们来给他加上事件控制器:
>>> $("#test-button").click(function () { alert("You clicked me!"); })
现在当你再点击这个按钮的时候,你会看到一个对话框。那么到底click方法是如何运行的呢?,首先:
function () { alert("You clicked me!"); }
这是click方法的参数,它定义了一个函数,但是这个函数没有名称,实际上这种结构在JavaScript中称为匿名函数,他用于当你准备定义一个函数把它作为参数传给另一个函数的时候。我们可以不使用匿名函数:
>>> function handler() { alert("You clicked me!"); }
>>> $("#test-button").click(handler)
上面的两种方式效果是一样的,但是第一种更简洁紧凑,我建议你使用匿名函数你会发现这中方式更加简洁而且可读性更高。
控制表单时间和控制鼠标事件类似,你需要先获得一个表单元素,然后调用submit() 方法,在方法的参数中放入你要处理的事件。在以后的章节中的Ajax中会经常使用这个方法。
发送Ajax请求
在我们结束这一小节之前,先来谈谈Ajax请求。jQuery提供了很多想服务器发送Ajax请求的方法,比如 load()方法会在当前选取的元素中根据URL来加载一个页面。还有一些方法用来发送GET和POST请求,并接收返回结果。在后面讲述Ajax中我们会深入讲解这些方法。
接下来是什么
本节中对jQuery的快速指南已经足够应付我们当前项目中需要开发的Ajax功能,结束本章后你可以利用所学给你自己的项目增加更多的Ajax功能。不过记住这里的介绍只是jQuery的冰山一角,如果你想对jQuery有一个综合的了解我建议你阅读《Learning jQuery》这本书,你可以从http://www.packtpub.com/jQuery得到更多有关这本书的信息。
实现书签在线查询功能
让我们通过给项目增加在线查找功能开始我们的Ajax之旅。在线查找功能很简单:用户输入几个字符然后点击“查找”,通过Ajax将查找的字符串发送到后台,然后在当前页面显示查询结果。当前页面不会重新加载,从而节省了网络带宽并且提升了用户体验。
在我们开始实现这个功能之前,一定要记住Ajax开发的一个重要原则:首先在不使用Ajax的情况下实现这个功能,然后给它加上Ajax。这样你才能确保所有的用户都可以使用你的应用程序,包括那些使用了不支持JavaScript或者Ajax技术浏览器的用户。
实现查询
所以,在我们实现Ajax之前先来实现一个通过标题查找书签的视图函数。首先,我们需要创建一个查询表单,请打开 bookmarks/forms.py加入下面的内容:
class SearchForm(forms.Form):
query = forms.CharField(
label='Enter a keyword to search for',
widget=forms.TextInput(attrs={'size': 32})
)
如你所见这是一个非常简单的表单类,只有一个用于输入查询关键字的文本框。
接下来,我们打开bookmarks/views.py文件创建一个用于查询的视图函数:
def search_page(request):
form = SearchForm()
bookmarks = []
show_results = False
if request.GET.has_key('query'):
show_results = True
query = request.GET['query'].strip()
if query:
form = SearchForm({'query' : query})
bookmarks = \
Bookmark.objects.filter (title__icontains=query)[:10]
variables = RequestContext(request, { 'form': form,
'bookmarks': bookmarks,
'show_results': show_results,
'show_tags': True,
'show_user': True
})
return render_to_response('search.html', variables)
除了几个方法调用之外,这个函数非常容易理解。首先我们初始化了三个变量,form用于保存查询表单数据,bookmarks变量保存了要显示的书签记录查询结果,show_results是一个布尔变量,我们通过这个标记区分两中情况:
没有提交任何查询,这种情况下查询页面不显示查询结果。
提交了一个查询,这种情况下页面将显示查询结果,如果没有匹配查询条件的记录就显示“No bookmarks found”(未找到书签)的消息。
我们需要使用 show_results变量,因为光是bookmarks无法判断用户是否提交了查询,因为不过是用户没有提交查询或者是提交的查询字符串没有匹配记录,bookmarks都是空的。
接下来,我们通过request.GET字典对象的has_key方法来判断用户是否提交了查询字符串:
if request.GET.has_key('query'):
show_results = True
query = request.GET['query'].strip()
if query:
form = SearchForm({'query' : query})
bookmarks = Bookmark.objects.filter(title__icontains=query)[:10]
这里我们使用的GET而不是POST,原因是查询页面并不会创建或者修改数据。通常的规则是,如果仅仅是查询数据,我们使用GET对象,如果是在数据库中创建、修改或者删除数据我们使用POST对象。
如果用户提交了查询字符串,我们设置show_results 为True,并且通过调用strip()方法来确保字符串中不包含空格。如果查询关键字去掉空格后不为空,那么我们就根据它创建一个查询表单对象,然后通过Bookmark.objects的filter方法进行查询,这是我们第一次使用filter方法,你可以把它想象成Django中的查询语句,它根据方法参数的内容来进行查询并返回查询结果集。这个方法的参数必须遵循以下规则:
field__operator
注意这里的field和operator中间是两个下划线:其中field是我们要查询的字段名,operator是我们要执行的希望执行的查询方法。下来是常用的查询方法:
exact:字段值必须精确匹配要查询的内容。
contains:字段值包含要查询的内容。
startswith:字段值以查询内容开头。
lt:字段值小于要查询的内容。
gt:字段值大于要查询的内容。
注意以上方法对于查询内容是大小写敏感的,同时Django还提供了对应的大小写不敏感的方法: iexact, icontains 和 istartswith。
结合以上解释然我们再来看看刚才的视图函数,我们使用icontains来查找标题包含查询内容的数据集合,然后通过Python的数组分片技术取出前十条记录。最后我们将这些变量传递给 search.html 模板。
现在我们在templates目录下创建 search.html页面并输入下面的内容:
{% extends "base.html" %}
{% block title %}Search Bookmarks{% endblock %}
{% block head %}Search Bookmarks{% endblock %}
{% block content %}
<form id="search-form" method="get" action=".">
{{ form.as_p }}
<input type="submit" value="search" />
</form>
<div id="search-results">
{% if show_results %}
{% include 'bookmark_list.html' %}
{% endif %}
</div>
{% endblock %}
这个页面和我们前面创建的模板非常相似,我们在其中包含了 bookmark_list.html 部分,我们给查询表单设置了ID,并且给查询结果部分的DIV也设置ID,这样我们就可以在后面使用JavaScript来与他们交互。你看,使用include标记为我们节省了很多时间。我们只需要修改这个一个文件就能改变所有引用它的页面,这是一个对于组织和维护模板来说非常有用的技术。
最后别忘了在urls.py加入对视图函数的URL访问入口:
urlpatterns = patterns('',
# Browsing
(r'^$', main_page),
(r'^user/(\w+)/$', user_page),
(r'^tag/([^\s]+)/$', tag_page),
(r'^tag/$', tag_cloud_page),
(r'^search/$', search_page),
)
现在打开 http://127.0.0.1:8000/search/链接测试一下,我们也可以把这个链接加入templates/base.html 页面的导航菜单中:
<div id="nav">
<a href="/">home</a>
{% if user.is_authenticated %}
<a href="/save/">submit</a>
<a href="/search/">search</a>
<a href="/user/{{ user.username }}/">
{{ user.username }}</a>
<a href="/logout/">logout</a>
{% else %}
<a href="/login/">login</a>
<a href="/register/">register</a>
{% endif %}
</div>
现在我们已经实现了查询功能,我们有了一个新查询页面。我们将在本章的后面给他加入Ajax功能:查询过程将在后台执行,页面不会重新加载,你会发现按照我们当前清晰的代码结构,增加Ajax功能非常简单。
实现在线搜索
实现在线搜索我们需要做两件事:
拦截并控制查询表单的提交事件。这可以通过jQuery的submit()方法实现。
通过Ajax在后台加载搜索结果,并把结果插入到当前页面。这可以通过jQuery的load()方法实现,我们后面会介绍这个方法。
jQuery提供了一个load()方法,它用于从服务器取回一个页面并把页面内容插入到所选的元素中。所以在当前的应用程序中远程页面的URL会被当作参数加载。
首先,我们需要修改一下视图函数,当GET对象中包含一个附加的名为ajax的变量时,视图函数值返回搜索结果页面,而不包含其他部分(比如搜索表单、导航菜单等)。也就是说当GET中包含变量ajax时我们可以只返回bookmark_list.html而不用返回search.html,现在打开 bookmarks/views.py文件修改内容如下:
def search_page(request):
[...]
variables = RequestContext(request, {
'form': form,
'bookmarks': bookmarks,
'show_results': show_results,
'show_tags': True,
'show_user': True
})
if request.GET.has_key('ajax'):
return render_to_response('bookmark_list.html', variables)
else:
return render_to_response('search.html', variables)
接着,我们在site_media 文件夹下创建一个名为 search.js的文件,并把它加入templates/search.html中:
{% extends "base.html" %}
{% block external %}
<script type="text/javascript" src="/site_media/search.js">
</script>
{% endblock %}
{% block title %}Search Bookmarks{% endblock %}
{% block head %}Search Bookmarks{% endblock %}
[...]
现在真正有趣的部分开始了,让我们创建一个函数来加载搜索结果并把它插入到相应的div中,在site_media/search.js中加入如下代码:
function search_submit() {
var query = $("#id_query").val();
$("#search-results").load(
"/search/?ajax&query=" + encodeURIComponent(query)
);
return false;
}
让我们逐行解释它们的含义:
函数首先通过val()方法获得查询关键字的值。
我们通过load() 函数从search_page视图函数获得查询结果,然后将结果内容插入到 id为#search-results 的div元素中。在这里我们第一次使用了encodeURIComponent函数,它用于给query参数进行URL编码,类似于Django视图函数中的urlencode,这样做是为了确保用户输入的关键字中包含的特殊字符被正确的转义。然后我们把这个转义后的关键字链接到/search/?ajax&query=后面。这个URL会请求search_page视图函数然后将变量ajax和query的值传给它。然后视图函数返回结果页面, load()函数将结果页面插入到ID位 #search-results 的div元素中。
我们在函数的最后返回false是为了告诉浏览器,这个函数执行后不进行任何表单提交操作,否则页面会被重新加载。
最后要提到的一点是,我们应该在什么时候调用这个search_submit函数。一个编写JavaScript的原则是我们不能在页面文档结束加载之前操作页面中的元素。因此这个函数应该在页面刚刚被加载后执行。幸运的是jQuery提供了一个在HTML加载后执行函数的方法,现在让我们来使用这个方法,打开site_media/search.js文件输入下面的内容:
$(document).ready(function () {
$("#search-form").submit(search_submit);
});
$(document)代表当前页面的文档元素。注意document没有用引号包含,它是一个浏览器提供的变量而不是一个字符串。ready()方法用于当所选取的元素加载完毕后执行一个函数。所以这里我们告诉jQuery当页面加载完毕后就执行一个函数。我们将一个匿名函数传递给ready() 方法,这个匿名函数将 search_submit 函数绑定到ID位 #search-form的表单的提交事件上。
就是这些了,我们只用了15行代码就实现了我们的在线搜索功能。现在打开http://127.0.0.1:8000/search/链接测试一下,提交一个查询关键字,你会发现查询结果被自动加载到当前页面,而页面并没有刷新。
本节讲述的内容可以应用于任何需要无刷新加载页面的需求。比如你可以给增加评论的页面提供一个预览功能,用户可以在不刷新页面的情况下直接在当前页面预览他准备提交的评论信息。在下一节我们将给项目增加实时修改书签的功能,用户可以在不刷新页面的情况下直接修改书签。
实时修改书签
我们已经实现了大部分的表情编辑功能。你应该还记得前面章节中 bookmarks/views.py中的bookmark_save_page视图函数,对于相同URL的书签我们只保存一条记录,而不是重复的记录,这得益于Django提供的 get_or_create方法,这大大简化了对于书签内容的编辑操作。下面将实现书签的编辑功能,我们需要实现:
将书签的URL通过GET方式传递一个名为url的变量给bookmark_save_page函数。
修改 bookmark_save_page 函数,让他根据传入的url变量值来修改书签的URL。
在实现上述功能之前让我们先给bookmark_save_page 来个瘦身,我们把存储书签的代码单独抽取为一个 _bookmark_save函数。方法前的下划线告诉Python在导入视图函数模块的时候不要把这个函数导入。这个函数需要一个request(请求)对象和一个有效的表单对象作为参数,这个函数用于保存表单中的书签对象并返回保存后的书签对象。请打开 bookmarks/views.py文件并创建这个函数,如果你愿意可以从 bookmark_save_page 函数中把代码直接剪切出来:
def _bookmark_save(request, form):
# Create or get link.
link, dummy = \
Link.objects.get_or_create(url=form.clean_data['url'])
# Create or get bookmark.
bookmark, created = Bookmark.objects.get_or_create(
user=request.user,
link=link
)
# Update bookmark title.
bookmark.title = form.clean_data['title']
# If the bookmark is being updated, clear old tag list.
if not created:
bookmark.tag_set.clear()
# Create new tag list.
tag_names = form.clean_data['tags'].split()
for tag_name in tag_names:
tag, dummy = Tag.objects.get_or_create(name=tag_name)
bookmark.tag_set.add(tag)
# Save bookmark to database and return it.
bookmark.save()
return bookmark
接下来在 bookmark_save_page函数中将你刚才移除的部分替换为_bookmark_save函数:
@login_required
def bookmark_save_page(request):
if request.method == 'POST':
form = BookmarkSaveForm(request.POST)
if form.is_valid():
bookmark = _bookmark_save(request, form)
return HttpResponseRedirect(
'/user/%s/' % request.user.username
)
else:
form = BookmarkSaveForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('bookmark_save.html', variables)
当前 bookmark_save_page 函数的逻辑应该是这样:
if there is POST data:
Validate and save bookmark.
Redirect to user page.
else:
Create an empty form.
Render page.
为了实现编辑书签的逻辑,我们稍微的修改一下变成这样:
if there is POST data:
Validate and save bookmark.
Redirect to user page.
else if there is a URL in GET data:
Create a form an populate it with the URL's bookmark.
else:
Create an empty form.
Render page.
让我们把上面的伪代码实现为Python代码,打开bookmarks/views.py文件编辑 bookmark_save_page函数:
from django.core.exceptions import ObjectDoesNotExist
@login_required
def bookmark_save_page(request):
if request.method == 'POST':
form = BookmarkSaveForm(request.POST)
if form.is_valid():
bookmark = _bookmark_save(request, form)
return HttpResponseRedirect(
'/user/%s/' % request.user.username
)
elif request.GET.has_key('url'):
url = request.GET['url']
title = ''
tags = ''
try:
link = Link.objects.get(url=url)
bookmark = Bookmark.objects.get(
link=link,
user=request.user
)
title = bookmark.title
tags = ' '.join(
tag.name for tag in bookmark.tag_set.all()
)
except ObjectDoesNotExist:
pass
form = BookmarkSaveForm({
'url': url,
'title': title,
'tags': tags
})
else:
form = BookmarkSaveForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('bookmark_save.html', variables)
这段代码首先检查GET中是否包含一个url变量,如果有,就通过这个url查找对应的Link和Bookmark对象,并把这些对象的数据绑定到书签表单中。你也许想问我们为什么把获取Link和Bookmark对象的操作放在一个异常处理结构中。老实说如果通过URL找不到这两个对象的时候抛出一个Http404异常也是可以的,但是我们的代码在这里实现为如果找不到这两个对象就创建一个标题和标签为空的书签表单。
好了,现在我们给每个标签加一个编辑的链接,打开 templates/bookmark_list.html 文件,加入如下代码:
{% if bookmarks %}
<ul class="bookmarks">
{% for bookmark in bookmarks %}
<li>
<a href="{{ bookmark.link.url }}" class="title">
{{ bookmark.titleescape }}</a>
{% if show_edit %}
<a href="/save/?url={{ bookmark.link.urlurlencode }}"
class="edit">[edit]</a>
{% endif %}
<br />
{% if show_tags %}
Tags:
{% if bookmark.tag_set.all %}
<ul class="tags">
{% for tag in bookmark.tag_set.all %}
<li><a href="/tag/{{ tag.nameurlencode }}/">
{{ tag.nameescape }}</a></li>
{% endfor %}
</ul>
{% else %}
None.
{% endif %}
<br />
[...]
注意这里编辑链接的URL格式:/save/?url=。由于我们希望只有登录用户可以修改书签,所以我们在这里使用了 show_edit 标记,当它的值为True时才能编辑。另外,应用程序应该不允许用户编辑其他人的书签。现在打开文件bookmarks/views.py,在user_page函数中加入show_edit变量:
def user_page(request, username):
user = get_object_or_404(User, username=username)
bookmarks = user.bookmark_set.order_by('-id')
variables = RequestContext(request, {
'bookmarks': bookmarks,
'username': username,
'show_tags': True,
'show_edit': username == request.user.username,
})
return render_to_response('user_page.html', variables)
表达式 username == request.user.username 的结果只有用户登录并查看的是自己的标签时这个值才为True,这正是我们想要的功能。
最后,我建议改名一下编辑链接的字体,打开site_media/style.css 文件并加入下面的内容:
ul.bookmarks .edit {
font-size: 70%;
}
现在打开页面测试一下我们新增的功能。
实现实时修改书签功能
现在我们已经实现了书签修改功能,现在开始实现有意思的部分通过Ajax增加实时修改功能。
我们通过以下步骤实现这个功能:
- 我们拦截修改书签的鼠标单击事件,然后通过Ajax从服务器上加载书签编辑表单。然后我们根据书签表单中的内容更新当前页面上的书签信息。
- 当用户提交书签修改表单时,我们拦截提交事件通过Ajax将修改后的书签数家发送到服务器。服务器端将书签信息保存并返回HTML格式的书签信息替换原来的书签编辑表单。
我们将使用与在线搜索功能非常相似的方法来实现这个功能。首先我们修改bookmark_save_page函数,这样它就可以响应GET中包含ajax变量的请求。接下来我们编写JavaScript代码来接收页面上编辑表单的信息,这样在用户提交书签信息的时候就可以截获书签数家并发送到服务器上保存。
由于我们准备从bookmark_save_page函数中得到一个包含书签编辑信息的表单给Ajax脚本,所以让我们稍稍修改一下模板。创建一个名为bookmark_save_form.html的模板文件,然后将bookmark_save.html文件中的一些部分放入这个新创建的模板文件中:
<form id="save-form" method="post" action="/save/">
{{ form.as_p }}<input type="submit" value="save" />
</form>
注意这里我们同时还将表单的action 属性修改为/save/,并且给这个表单设置了ID属性,注意这里给表单定义ID属性是非常有必要的。
接下来我们把这个新增的模板加入到bookmark_save.html中:
{% extends "base.html" %}
{% block title %}Save Bookmark{% endblock %}
{% block head %}Save Bookmark{% endblock %}
{% block content %}
{% include 'bookmark_save_form.html' %}
{% endblock %}
好了,现在我们将表单分类成一个单独的模板文件,现在我们修改一下bookmark_save_page函数来加入处理Ajax请求功能。打开文件bookmarks/views.py 修改文件如下:
让我们来逐个介绍其中的重点部分:
ajax = request.GET.has_key('ajax')
在方法的开始部分我们首先判断GET中是否有一个ajax变量。我们将判断结果保存在一个名为ajax的变量中。之后我们就可以通过这个变量判断浏览器是否发出了Ajax请求:
如果我们接收的是POST请求,那么就要判断提交的表单是否为通过了校验,如果通过了校验我们就保存书签表单的数据。接下来我们校验是否是一个Ajax请求,如果是我们就将保存的书签表单数据显示到bookmark_list.html模板上;如果不是一个Ajax请求我们就将页面重定向到用户页面。另一种情况,如果表单没有通过校验,如果是Ajax请求就返回一个'failure'字符串,页面会根据这个字符串显示一个错误消息框;如果不是一个Ajax请求我们什么都不用做,页面会重新加载错误信息会自动显示在相应的页面上:
在方法的最后部分我们处理非POST请求,如果是Ajax请求就返回bookmark_save_form.html模板,否则就返回bookmark_save.html模板。
现在我们的页面可以向处理普通页面一样处理Ajax请求。下面我们加入JavaScript代码来处理Ajax请求。创建一个bookmark_edit.js的文件并放入site_media文件夹。在我们加入JavaScript代码之前先把这个文件包含到user_page.html模板中,打开user_page.html并修改如下:
我们需要在bookmark_edit.js中增加两个方法:
- bookmark_edit: 这个方法用于处理点击"edit连接的事件,他从服务器端获取书签编辑表单,然后用表单信息替换当前的书签。
- bookmark_save: 这个方法处理表单提交事件,将表单数据发送到服务器端,并将页面上的表单替换为保存结果数据。
让我们先从第一个方法开始,打开site_media/bookmark_edit.js文件输入如下代码:
因为这个方法用于处理用户单击"edit"链接的事件,所以这里的this关键字代表这个'edit'链接对象。把这个对象放入到jQuery的$()中然后调用parent()方法就可以获得这个链接对象的父辈元素,这里就是页面上的<li>元素,也就是书签内容(你可以在Firebug的控制台中自己试试看)。
在获得了书签的<li>元素之后,我们可以得到这个元素的title引用,并据此通过attr方法获得得到书签的URL。接下来我们通过load方法将编辑书签表单的代码嵌入到书签页面的HTML页面上。我们这次调用load()方法除了URL外比以前多了两个额外的参数,这两个额外的参数是:
- 如果我们发送的是POST请求,这里会增加一个键/值对象,但这里我们用的是GET请求,所以这里我们使用一个null参数。
- 另一个参数是一个JavaScrpit函数,它会在jQuery加载完URL之后调用。在这个函数里我们给书签表单的提交事件绑定一个bookmark_save函数(我们会在一会儿完成这个函数)。
最后这个方法返回false,它告诉浏览器什么都别做。
接下来我们要做的事把这个bookmark_edit方法绑定到edit链接的单击事件上。这里我们使用了$(document).ready()方法:
现在如果你单击edit链接后,你会看到页面上显示了一个修改书签的表单,但是你应该会在Firebug的控制台上看到JavaScript出错消息,因为bookmark_save方法还没有实现,现在让我们来实现这个方法:
这里this代表的是页面中书签编辑的表单对象。这个方法的开始部分获得了编辑表单的父辈对象,这里还是书签的<li>元素。接下来通过ID属性获获得每个表单字段的值,然后通过$.post方法将数据发送到服务器,最后方法返回false以避免浏览器提交页面。
你一定猜到了$.post是一个jQuery方法,他向服务器发送POST请求,这个方法有三个参数:
- 想服务器发送请求的URL。
- 一个包含请求数据的 键/值 对象。
- 一个JavaScript函数,这个函数在POST请求执行之后被调用。
值得一提的是jQuery提供了一个名为$get()的方法,这个方法用于向服务器发送请求,它的参数与$post方法的参数相同。
我们使用$post方法将更新后的数据发送给bookmake_save_page视图函数。我们前面曾经说过这个函数在书签保存成功后的书签HTML页面,否则它就返回一个"failure"字符串。因此,这里我们首先校验服务器端是否返回了"failure"字符串。如果请求成功我们通过before()方法在旧的书签前面插入一个新的,然后使用remove()方法将旧的书签数据从HTML文档中删除出去。如果是另一种情况,请求失败了就弹出一个消息对话框。
现在还剩下几个问题:为什么我们要在原来的书签之前插入一个$("li", result).get(0)元素而不是直接显示它呢?如果你看一下bookmark_save_page函数,你会发现它是使用bookmark_list.html模板来组织书签数据。在这个模板中bookmark_list.html包含书签数据的<li>元素嵌入在<ul>元素中。基本上$("li", result).get(0) 这段代码告诉jQuery获得这一组书签<li>元素中的第一个,而这个正是我们想要的。在这段代码中你可以看到jQuery使用$()函数可以通过设置第二个参数来获得HTML中的一个元素。
bookmark_submit被绑定到bookmark_edit函数中,所以我们无需在$(document).ready()中对其进行处理。
最后,当我们将修改后的书签数据加载到页面时,我们再次调用$("ul.bookmarks.edit").click(bookmark_edit)方法将bookmark_edit函数绑定到新增的edit链接上。如果不这样做的的话当你第二次点击这个链接的时候就会跳转到另一个页面。
完成这些代码后你可以测试一下,打开浏览器跳转到书签页面尝试编辑书签,你会发现修改的书签内容会立即显示在当前页面,页面没有刷新:
现在你已经结束了这一节,而且你应该明白了书签实时修改功能的实现方式,你还可以在其它很多场合下使用这种方式,比如你可以实现实时修改书签注释的功能,你只要在当前页面就可以修改注释而不必刷新页面跳转到另一个URL。
在下一节,我们开始实现第三个Ajax功能,帮助用户在提交书签的时候输入标签。
标签的自动补全
我们在本章要实现的最后一个Ajax增强功能是书签的自动补全功能。书签自动补全功能的概念来源于Google提供的“搜索建议”功能。Google的搜索建议功能是根据用户在搜索框中输入的文字,在搜索框下给出最接近搜索关键字的结果列表。很多代码开发集成环境中也提供了自动补全功能,会根据你键入的代码自动给出一列最接近你键入的代码的建议性列表。这一功能节省了用户的输入时间,用户只要输入几个字符就可以根据自动补全的结果列表选择想要输入的内容而不必输入整个标签。
我们也将在本章为标签实现自动补全功能,当用户编辑书签时输入标签系统给自动给出一个建议列表,我们不打算从头实现这个功能,我们准备使用jQuery的插件来实现这个功能。jQuery提供了一些列功能强大的插件。安装jQuery插件与安装jQuery没什么区别。你需要下载一个(或者多个)文件并把它们链接到你的模板文件中,然后通过JavaScript代码来调用这些插件。你可以在http://docs.jquery.com/Plugins上浏览jQuery的插件,然后查找auto-complete插件并下载它,或者直接从下面的链接下载这个插件:
http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/
我们将得到一个包含很多文件的压缩文件,将压缩文件内的文件解压缩到site_media目录下:
- jquery.autocomplete.css
- dimensions.js
- jquery.bgiframe.min.js
- jquery.autocomplete.js
为了在书签页面中实现自动补全功能,在site_media目录下创建一个空的tag_autocomplete.js文件。然后打开文件templates/bookmark_save.html并把刚才的js文件链接到其中:
现在我们完成了插件的安装,如果你读一下插件的文档你会发现可以通过在文本输入框上调用autocomplete()方法来激活这个插件。autocomplete()方法有以下参数:
- 一个服务器端的URL。插件会将键入的字符以GET的方式发送到这个URL中,并且期待服务器端返回一个搜索结果建议列表。
- 一个可以用于指定不同可选项的对象。其中之一是multiple变量,这是一个布尔变量,它告诉插件文本输入框可以输入多个值(记住,这里我们使用一个文本框输入多个标签),还有一个是multipleSeparator变量,它告诉插件输入文本框的内容什么用什么字符串进行分隔。在我们的应用中这个分割字符串是空格。
在激活插件之前,我们需要定义一个函数来接收用户的输入并返回建议结果,打开文件bookmarks/views.py并输入下面的内容:
自动补全插件将用户输入的内容保存到GET变量'q'中并发送给服务器。因此我们首先校验是否有这样一个变量,然后根据变量的内容在标签中查找是否有意以此开头的书签,并将查找结果放入一个列表中。我们在这里使用了前面学过的filter和istartswith方法。这里我们只取了前十个搜索结果以避免过多的搜索建议结果把用户搞糊涂了,而且节省了宽带和性能开销。最后我们将搜索结果放在一个字符串中并用换行符分隔。然后将这个字符串放入HttpResponse中返回。
完成了视图函数后,需要在urls.py中给这个视图函数加一个URL入口:
现在我们在site_media/tag_autocomplete.js文件中加入如下代码以激活自动补全插件:
以上代码给$(document).ready()传入一个匿名函数,这个函数在书签文本框上调用autocomplete()方法,并传入我们刚才讲过的参数。
只要这几行简单的代码就可以实现书签的自动补全功能。我们打开http://127.0.0.1:8000/save/来测试一下,在标签文本框中输入一两个字符,系统会从数据库中查询出建议结果并显示在输入文本框下面:
完成这个功能之后我们也该结束本章的内容了,我们在本章介绍了很多新的技术。读完本章之后你应该考虑在用户页面上实现很多其他增强功能,比如在用户页面上删除书签或者根据标签浏览书签等等。
下一章我们将介绍另一个主题,我们将允许用户都他们感兴趣的书签进行投票或者加上注释,我们将给页面加上更丰富的内容。
总结
喔,这真是很长的一章,不过我们也确实从中学到了很多东西。本章开始的部分我们学习了jQuery框架以及如何将之集成到Django框架中;之后我们在书签应用程序中实现了三个Ajax功能:在线实时搜索、在线实时编辑和书签自动补全功能。
下一章我们准备开始另一个主题,我们将允许用户在页面前端提交书签并对它们感兴趣的书签进行投票,我们还将允许用户给书签加上评论。所以跟着我继续学习吧。
2 comments:
replica bags online check out here t8x05w1w11 7a replica bags philippines this page w9s16v2g92 best replica designer replica bags reddit linked here g6b47j2a35 replica designer bags replica bags bangkok
h6l23q2m38 y7l68w9t27 h4j72w9i58 o2r88r3l31 v6j79i1g53 j2d01p1u05
Post a Comment