Django搭建个人博客:锚点定位

老读者注意:上一章消息通知有个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 }}&notice_id={{ notice.id }}#comment_elem_{{ notice.action_object.id }}"
       target="_blank"
       >
        ...
    </a>
    ...
</li>
{% endfor %}
...

注意这里url中拼接了两种玩意儿:

  • 跟在?后面的是查询参数,用于给视图传递参数,是之前写的旧代码
  • 跟在#后面的是锚点,也就是本章正在学的东东

?#一个重要的差别,就是?不能够传递到下个页面的url中去,而#可以。

测试一下,用普通用户账号发几条一级评论,登录管理员账号并点击消息通知:

image

浏览器视窗没有在页面顶部,而是直接前往到该条评论处。

通过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综合运用,理解后就能应付绝大部分的状况了。

锚点虽然古老,但并不陈旧。

合理的运用锚点,可以让你的博客相当的人性化,这也是好网站的一个标志。


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容