共计 4142 个字符,预计需要花费 11 分钟才能阅读完成。
老读者注意:上一章消息通知有个 bug,即发给管理员的
notify
必须移动到new_comment.save()
的后面,否则会导致action_object
存储为NULL
,并且导致本章的html
拼接锚点失效。原文已更正,为博主的疏忽表示歉意。
上一章已经实现了 消息通知功能,可以很人性化的把用户引导到被他人回复的页面中去。
但是仔细想想,似乎还有不方便的地方:如果页面中评论较多,想找到感兴趣的那一条评论还是要费点功夫的。所以这个消息通知,最好是能够不仅前往正确的页面,还要前往正确的位置(需求是无穷无尽的..)。
为了实现这个功能,本章就要介绍一个非常古老的功能:锚点定位。以及如何在 Django 中实现它。
锚点是什么
我们在写 html
文件的容器时,经常会用到 id
属性:
<div id="fruit">apple</div>
这个 id
属性不仅可以作为 Javascript
或者 css
代码查询某个容器的标记,还可以作为锚点,定位页面应该前往的位置。输入下面的地址:
http://www.myblog.com/home#fruit
浏览器就会打开 home
页面,并且视窗前往 id="fruit"
的容器。
明白了锚点是什么,下面就通过 三种不同的实现方法,看看锚点在 Django 博客项目中是如何应用的。
三种实现
html 拼接
锚点首先要实现的功能,就是 当管理员点击消息通知时,浏览器视窗前往此通知的评论位置。
因此首先修改文章详情页面,给渲染评论的 div
容器添加 id
属性:
templates/article/detail.html
...
<!-- 已有代码,遍历树形结构 -->
{% recursetree comments %}
{% with comment=node %}
<!-- 唯一新增代码:id 属性 -->
<div class="..." id="comment_elem_{{comment.id}}" >
...
<!-- 下面都是已有代码 -->
<div class="children">
{{children}}
</div>
{% endif %}
</div>
{% endwith %}
{% endrecursetree %}
...
我们还是用 comment.id
来给每条评论赋予唯一的 id
值。注意 id
属性保持 唯一性 。前面在二级回复的Modal 中用了comment_{{comment.id}}
,这里千万不要重复了。
然后修改通知列表模板,添加锚点:
templates/notice/list.html
...
{% for notice in notices %}
<li ...>
<!-- 新增 comment_elem_{{notice.action_object.id}} 锚点 -->
<a href="{% url"notice:update"%}?article_id={{notice.target.id}}¬ice_id={{notice.id}}#comment_elem_{{notice.action_object.id}}"
target="_blank"
>
...
</a>
...
</li>
{% endfor %}
...
注意这里 url
中拼接了两种玩意儿:
- 跟在
?
后面的是查询参数,用于给视图传递参数,是之前写的旧代码 - 跟在
#
后面的是锚点,也就是本章正在学的东东
?
和 #
一个重要的差别,就是 ?
不能够传递到下个页面的 url
中去,而 #
可以。
测试一下,用普通用户账号发几条一级评论,登录管理员账号并点击消息通知:
浏览器视窗没有在页面顶部,而是直接前往到该条评论处。
通过 html
拼接是实现锚点最 简单直接 的方法。
视图拼接
html
拼接虽好,但它不是万能的。如果要 前往一个当前页面还没有创建的容器,该怎么办?
举个栗子。按照目前我们的博客设计,当用户发表评论时,页面会刷新、视窗将停留在文章详情的顶部。但实际上这时候视窗应该停留在新发表的评论处才比较合理,因为用户可能想检查一下自己发表的评论是否正确。而在原页面时由于新评论都还没发表,所以 comment.id
是不存在的,没办法用 html
拼接锚点。读者好好思考一下是不是这样。
这种情况下就需要在视图中拼接锚点了。修改文章评论视图,将锚点拼接到 redirect
函数中:
comment/views.py
...
# 文章评论视图
def post_comment(request, article_id, parent_comment_id=None):
...
# 已有代码
if request.method == 'POST':
...
if comment_form.is_valid():
...
if parent_comment_id:
...
new_comment.save()
if not request.user.is_superuser:
notify.send(...)
# 新增代码,添加锚点
redirect_url = article.get_absolute_url() + '#comment_elem_' + str(new_comment.id)
# 修改 redirect 参数
return redirect(redirect_url)
get_absolute_url()
是之前章节写的方法,用于查询某篇文章的地址。
说白了就是把拼接的位置从 模板 挪到了 视图 中,因为新评论必须在视图中保存之后才会被分配一个 id
值。
流动的数据
最后我们来看稍微复杂点的情况。
当用户发表一级评论时,我们在视图中拼接锚点解决了刷新当前页面并定位的问题。但是 二级评论 是通过 iframe + ajax
实现的,这又该怎么办?
理一理思路。
首先,新评论的 id
值是在视图中创建的,但是由于视图是从 iframe
中请求的,在视图中没办法刷新 iframe
的父页面。所以我们唯一能做的就是 把数据传递出去,到前端去处理。
修改文章评论视图:
comment/views.py
# 引入 JsonResponse
from django.http import JsonResponse
...
# 文章评论视图
def post_comment(request, article_id, parent_comment_id=None):
article = get_object_or_404(ArticlePost, id=article_id)
# 已有代码
if request.method == 'POST':
...
if comment_form.is_valid():
...
if parent_comment_id:
...
# 修改此处代码
# return HttpResponse("200 OK")
return JsonResponse({"code": "200 OK", "new_comment_id": new_comment.id})
...
新引入的 JsonResponse
返回的是 json
格式的数据,由它将新评论的 id
传递出去。
json 是 web 开发中很常用的轻量级数据格式,非常像 python 的字典,读者请自行了解。
特别提醒 json 格式必须用双引号。
现在数据在 iframe
中了。但是我们需要刷新的是 iframe
的父页面啊,所以还要 继续把数据往父页面“扔 ”。
修改二级评论的模板:
templates/comment/reply.html
...
<script>
...
function confirm_submit(article_id, comment_id){
...
$.ajax({
...
// 成功回调函数
success: function(e){
// 旧代码
// if(e === '200 OK'){// parent.location.reload();
// };
// 新代码
if(e.code === '200 OK'){
// 调用父页面的函数
parent.post_reply_and_show_it(e.new_comment_id);
};
}
});
}
</script>
由于现在 ajax 获取的是 json 数据,因此用 e.code
获取视图返回的状态。
旧代码用 parent.location.reload()
刷新了父页面。同样的,用 parent.abc()
可以调用父页面的 abc()
函数。这样就把数据传递到父页面里去了。
这下就好说了。在父页面中(文章详情模板)添加需要执行锚点拼接的函数:
templates/article/detail.html
...
{% block script %}
...
<script>
...
// 新增函数,处理二级回复
function post_reply_and_show_it(new_comment_id) {let next_url = "{% url'article:article_detail'article.id %}";
// 去除 url 尾部 '/' 符号
next_url = next_url.charAt(next_url.length - 1) == '/' ? next_url.slice(0, -1) : next_url;
// 刷新并定位到锚点
window.location.replace(next_url + "#comment_elem_" + new_comment_id);
};
</script>
{% endblock script %}
函数中运用了 JavaScript 的三元运算符 a ? b : c
,翻译成人话就是:如果a
成立则返回 b
,如果a
不成立就返回 c
。作用是去掉url
尾部的 /
,否则锚点不会生效。你可能会问,三元运算符多麻烦,为什么不直接把url
末尾一个字符剔除掉呢?答案是这样写代码更加健壮。万一哪天 Django 解析的 url
尾部没有斜杠了呢。
window.location.replace()
作用是重定向页面,在这里面终于可以愉快的拼接锚点了。
一切都 OK 啦。测试发表二级评论,运气好的同学应该可以顺利将视窗定位到刚评论的位置了。
感受到数据的流动没有?
总结
本章学习了锚点的 html 拼接、视图拼接、ajax+iframe 综合运用,理解后就能应付绝大部分的状况了。
锚点虽然古老,但并不陈旧。
合理的运用锚点,可以让你的博客相当的人性化,这也是好网站的一个标志。
- 有疑问请在杜赛的个人网站留言,我会尽快回复。
- 或 Email 私信我:dusaiphoto@foxmail.com
- 项目完整代码:Django_blog_tutorial