**老讀者注意:**上一章消息通知有個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私信我:[email protected]
- 項目完整代碼:Django_blog_tutorial