Django搭建個(gè)人博客:錨點(diǎn)定位

老讀者注意:上一章消息通知有個(gè)bug,即發(fā)給管理員的notify必須移動(dòng)到new_comment.save()的后面,否則會導(dǎo)致action_object存儲為NULL,并且導(dǎo)致本章的html拼接錨點(diǎn)失效。

原文已更正,為博主的疏忽表示歉意。

上一章已經(jīng)實(shí)現(xiàn)了消息通知功能,可以很人性化的把用戶引導(dǎo)到被他人回復(fù)的頁面中去。

但是仔細(xì)想想,似乎還有不方便的地方:如果頁面中評論較多,想找到感興趣的那一條評論還是要費(fèi)點(diǎn)功夫的。所以這個(gè)消息通知,最好是能夠不僅前往正確的頁面,還要前往正確的位置(需求是無窮無盡的..)。

為了實(shí)現(xiàn)這個(gè)功能,本章就要介紹一個(gè)非常古老的功能:錨點(diǎn)定位。以及如何在Django中實(shí)現(xiàn)它。

錨點(diǎn)是什么

我們在寫html文件的容器時(shí),經(jīng)常會用到id屬性:

<div id="fruit">apple</div>

這個(gè)id屬性不僅可以作為Javascript或者css代碼查詢某個(gè)容器的標(biāo)記,還可以作為錨點(diǎn),定位頁面應(yīng)該前往的位置。輸入下面的地址:

http://www.myblog.com/home#fruit

瀏覽器就會打開home頁面,并且視窗前往id="fruit"的容器。

明白了錨點(diǎn)是什么,下面就通過三種不同的實(shí)現(xiàn)方法,看看錨點(diǎn)在Django博客項(xiàng)目中是如何應(yīng)用的。

三種實(shí)現(xiàn)

html拼接

錨點(diǎn)首先要實(shí)現(xiàn)的功能,就是當(dāng)管理員點(diǎn)擊消息通知時(shí),瀏覽器視窗前往此通知的評論位置。

因此首先修改文章詳情頁面,給渲染評論的div容器添加id屬性:

templates/article/detail.html

...
<!-- 已有代碼,遍歷樹形結(jié)構(gòu) -->
{% 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屬性保持唯一性。前面在二級回復(fù)的Modal中用了comment_{{ comment.id }},這里千萬不要重復(fù)了。

然后修改通知列表模板,添加錨點(diǎn):

templates/notice/list.html

...
{% for notice in notices %}
<li ...>
    <!-- 新增 comment_elem_{{ notice.action_object.id }} 錨點(diǎn) -->
    <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中拼接了兩種玩意兒:

  • 跟在?后面的是查詢參數(shù),用于給視圖傳遞參數(shù),是之前寫的舊代碼
  • 跟在#后面的是錨點(diǎn),也就是本章正在學(xué)的東東

?#一個(gè)重要的差別,就是?不能夠傳遞到下個(gè)頁面的url中去,而#可以。

測試一下,用普通用戶賬號發(fā)幾條一級評論,登錄管理員賬號并點(diǎn)擊消息通知:

image

瀏覽器視窗沒有在頁面頂部,而是直接前往到該條評論處。

通過html拼接是實(shí)現(xiàn)錨點(diǎn)最簡單直接的方法。

視圖拼接

html拼接雖好,但它不是萬能的。如果要前往一個(gè)當(dāng)前頁面還沒有創(chuàng)建的容器,該怎么辦?

舉個(gè)栗子。按照目前我們的博客設(shè)計(jì),當(dāng)用戶發(fā)表評論時(shí),頁面會刷新、視窗將停留在文章詳情的頂部。但實(shí)際上這時(shí)候視窗應(yīng)該停留在新發(fā)表的評論處才比較合理,因?yàn)橛脩艨赡芟霗z查一下自己發(fā)表的評論是否正確。而在原頁面時(shí)由于新評論都還沒發(fā)表,所以comment.id是不存在的,沒辦法用html拼接錨點(diǎn)。讀者好好思考一下是不是這樣。

這種情況下就需要在視圖中拼接錨點(diǎn)了。修改文章評論視圖,將錨點(diǎn)拼接到redirect函數(shù)中:

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(...)

            # 新增代碼,添加錨點(diǎn)
            redirect_url = article.get_absolute_url() + '#comment_elem_' + str(new_comment.id)
            # 修改redirect參數(shù)
            return redirect(redirect_url)

get_absolute_url()是之前章節(jié)寫的方法,用于查詢某篇文章的地址。

說白了就是把拼接的位置從模板挪到了視圖中,因?yàn)樾略u論必須在視圖中保存之后才會被分配一個(gè)id值。

流動(dòng)的數(shù)據(jù)

最后我們來看稍微復(fù)雜點(diǎn)的情況。

當(dāng)用戶發(fā)表一級評論時(shí),我們在視圖中拼接錨點(diǎn)解決了刷新當(dāng)前頁面并定位的問題。但是二級評論是通過iframe + ajax實(shí)現(xiàn)的,這又該怎么辦?

理一理思路。

首先,新評論的id值是在視圖中創(chuàng)建的,但是由于視圖是從iframe中請求的,在視圖中沒辦法刷新iframe的父頁面。所以我們唯一能做的就是把數(shù)據(jù)傳遞出去,到前端去處理。

修改文章評論視圖:

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格式的數(shù)據(jù),由它將新評論的id傳遞出去。

json是web開發(fā)中很常用的輕量級數(shù)據(jù)格式,非常像python的字典,讀者請自行了解。

特別提醒json格式必須用雙引號。

現(xiàn)在數(shù)據(jù)在iframe中了。但是我們需要刷新的是iframe的父頁面啊,所以還要繼續(xù)把數(shù)據(jù)往父頁面“扔"。

修改二級評論的模板:

templates/comment/reply.html

...
<script>
...

function confirm_submit(article_id, comment_id){
    ...
    $.ajax({
        ...
        // 成功回調(diào)函數(shù)
        success: function(e){
            
            // 舊代碼
            // if(e === '200 OK'){
            //     parent.location.reload();
            // };
            
            // 新代碼
            if(e.code === '200 OK'){
                // 調(diào)用父頁面的函數(shù)
                parent.post_reply_and_show_it(e.new_comment_id);
            };
        }
    });
}
</script>

由于現(xiàn)在ajax獲取的是json數(shù)據(jù),因此用e.code獲取視圖返回的狀態(tài)。

舊代碼用parent.location.reload()刷新了父頁面。同樣的,用parent.abc()可以調(diào)用父頁面的abc()函數(shù)。這樣就把數(shù)據(jù)傳遞到父頁面里去了。

這下就好說了。在父頁面中(文章詳情模板)添加需要執(zhí)行錨點(diǎn)拼接的函數(shù):

templates/article/detail.html

...

{% block script %}
...
<script>
    ...

    // 新增函數(shù),處理二級回復(fù)
    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;
        // 刷新并定位到錨點(diǎn)
        window.location.replace(next_url + "#comment_elem_" + new_comment_id);
    };
</script>
{% endblock script %}

函數(shù)中運(yùn)用了JavaScript三元運(yùn)算符a ? b : c,翻譯成人話就是:如果a成立則返回b,如果a不成立就返回c。作用是去掉url尾部的/,否則錨點(diǎn)不會生效。你可能會問,三元運(yùn)算符多麻煩,為什么不直接把url末尾一個(gè)字符剔除掉呢?答案是這樣寫代碼更加健壯。萬一哪天Django解析的url尾部沒有斜杠了呢。

window.location.replace()作用是重定向頁面,在這里面終于可以愉快的拼接錨點(diǎn)了。

一切都OK啦。測試發(fā)表二級評論,運(yùn)氣好的同學(xué)應(yīng)該可以順利將視窗定位到剛評論的位置了。

感受到數(shù)據(jù)的流動(dòng)沒有?

總結(jié)

本章學(xué)習(xí)了錨點(diǎn)的html拼接、視圖拼接、ajax+iframe綜合運(yùn)用,理解后就能應(yīng)付絕大部分的狀況了。

錨點(diǎn)雖然古老,但并不陳舊。

合理的運(yùn)用錨點(diǎn),可以讓你的博客相當(dāng)?shù)娜诵曰?,這也是好網(wǎng)站的一個(gè)標(biāo)志。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容