第三章:模板擴(kuò)展

2021-08-31 18:13 更新

在第二章中,我們看到了Tornado模板系統(tǒng)如何簡(jiǎn)單地傳遞信息給網(wǎng)頁(yè),使你在插入動(dòng)態(tài)數(shù)據(jù)時(shí)保持網(wǎng)頁(yè)標(biāo)記的整潔。然而,大多數(shù)站點(diǎn)希望復(fù)用像header、footer和布局網(wǎng)格這樣的內(nèi)容。在這一章中,我們將看到如何使用擴(kuò)展Tornado模板或UI模塊完成這一工作。

3.1 塊和替換

當(dāng)你花時(shí)間為你的Web應(yīng)用建立和制定模板時(shí),希望像你的后端Python代碼一樣重用你的前端代碼似乎只是合邏輯的,不是嗎?幸運(yùn)的是,Tornado可以讓你做到這一點(diǎn)。Tornado通過(guò)extends和block語(yǔ)句支持模板繼承,這就讓你擁有了編寫能夠在合適的地方復(fù)用的流體模板的控制權(quán)和靈活性。

為了擴(kuò)展一個(gè)已經(jīng)存在的模板,你只需要在新的模板文件的頂部放上一句{% extends "filename.html" %}。比如,為了在新模板中擴(kuò)展一個(gè)父模板(在這里假設(shè)為main.html),你可以這樣使用:

{% extends "main.html" %}

這就使得新文件繼承main.html的所有標(biāo)簽,并且覆寫為期望的內(nèi)容。

3.1.1 塊基礎(chǔ)

擴(kuò)展一個(gè)模板使你復(fù)用之前寫過(guò)的代碼更加簡(jiǎn)單,但是這并不會(huì)為你提供所有的東西,除非你可以適應(yīng)并改變那些之前的模板。所以,block語(yǔ)句出現(xiàn)了。

一個(gè)塊語(yǔ)句壓縮了一些當(dāng)你擴(kuò)展時(shí)可能想要改變的模板元素。比如,為了使用一個(gè)能夠根據(jù)不同頁(yè)覆寫的動(dòng)態(tài)header塊,你可以在父模板main.html中添加如下代碼:

<header>
    {% block header %}{% end %}
</header>

然后,為了在子模板index.html中覆寫{% block header %}{% end %}部分,你可以使用塊的名字引用,并把任何你想要的內(nèi)容放到其中。

{% block header %}{% end %}

{% block header %}
    <h1>Hello world!</h1>
{% end %}

任何繼承這個(gè)模板的文件都可以包含它自己的{% block header %}{% end %},然后把一些不同的東西加進(jìn)去。

為了在Web應(yīng)用中調(diào)用這個(gè)子模板,你可以在你的Python腳本中很輕松地渲染它,就像之前你渲染其他模板那樣:

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html")

所以此時(shí),main.html中的body塊在加載時(shí)會(huì)被以index.html中的信息"Hello world!"填充(參見圖3-1)。

圖3-1

圖3-1 Hello world!

我們已經(jīng)可以看到這種方法在處理整體頁(yè)面結(jié)構(gòu)和節(jié)約多頁(yè)面網(wǎng)站的開發(fā)時(shí)間上多么有用。更好的是,你可以為每個(gè)頁(yè)面使用多個(gè)塊,此時(shí)像header和footer這樣的動(dòng)態(tài)元素將會(huì)被包含在同一個(gè)流程中。

下面是一個(gè)在父模板main.html中使用多個(gè)塊的例子:

<html>
<body>
    <header>
        {% block header %}{% end %}
    </header>
    <content>
        {% block body %}{% end %}
    </content>
    <footer>
        {% block footer %}{% end %}
    </footer>
</body>
</html>

當(dāng)我們擴(kuò)展父模板main.html時(shí),可以在子模板index.html中引用這些塊。

{% extends "main.html" %}

{% block header %}
    <h1>{{ header_text }}</h1>
{% end %}

{% block body %}
    <p>Hello from the child template!</p>
{% end %}

{% block footer %}
    <p>{{ footer_text }}</p>
{% end %}

用來(lái)加載模板的Python腳本和上一個(gè)例子差不多,不過(guò)在這里我們傳遞了幾個(gè)字符串變量給模板使用(如圖3-2):

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render(
            "index.html",
            header_text = "Header goes here",
            footer_text = "Footer goes here"
        )

圖3-2

圖3-2 塊基礎(chǔ)

你也可以保留父模板塊語(yǔ)句中的默認(rèn)文本和標(biāo)記,就像擴(kuò)展模板沒有指定它自己的塊版本一樣被渲染。這種情況下,你可以根據(jù)某頁(yè)的情況只替換必須的東西,這在包含或替換腳本、CSS文件和標(biāo)記塊時(shí)非常有用。

正如模板文檔所記錄的,"錯(cuò)誤報(bào)告目前...呃...是非常有意思的"。一個(gè)語(yǔ)法錯(cuò)誤或者沒有閉合的{% block %}語(yǔ)句可以使得瀏覽器直接顯示500: Internal Server Error(如果你運(yùn)行在debug模式下會(huì)引發(fā)完整的Python堆棧跟蹤)。如圖3-3所示。

總之,為了你自己好的話,你需要使自己的模板盡可能的魯棒,并且在模板被渲染之前發(fā)現(xiàn)錯(cuò)誤。

圖3-3

圖3-3 塊錯(cuò)誤

3.1.2 模板練習(xí):Burt's Book

所以,你會(huì)認(rèn)為這聽起來(lái)很有趣,但卻不能描繪出在一個(gè)標(biāo)準(zhǔn)的Web應(yīng)用中如何使用?那么讓我們?cè)谶@里看一個(gè)例子,我們的朋友Burt希望運(yùn)行一個(gè)名叫Burt's Books的書店。

Burt通過(guò)他的書店賣很多書,他的網(wǎng)站會(huì)展示很多不同的內(nèi)容,比如新品推薦、商店信息等等。Burt希望有一個(gè)固定的外觀和感覺的網(wǎng)站,同時(shí)也能更簡(jiǎn)單的更新頁(yè)面和段落。

為了做到這些,Burt's Book使用了以Tornado為基礎(chǔ)的網(wǎng)站,其中包括一個(gè)擁有樣式、布局和header/footer細(xì)節(jié)的主模版,以及一個(gè)處理頁(yè)面的輕量級(jí)的子模板。在這個(gè)系統(tǒng)中,Burt可以把最新發(fā)布、員工推薦、即將發(fā)行等不同頁(yè)面編寫在一起,共同使用通用的基礎(chǔ)屬性。

Burt's Book的網(wǎng)站使用一個(gè)叫作main.html的主要基礎(chǔ)模板,用來(lái)包含網(wǎng)站的通用架構(gòu),如下面的代碼所示:

<html>
<head>
    <title>{{ page_title }}</title>
    <link rel="stylesheet" href="{{ static_url("css/style.css") }}" />
</head>
<body>
    <div id="container">
        <header>
            {% block header %}<h1>Burt's Books</h1>{% end %}
        </header>
        <div id="main">
            <div id="content">
                {% block body %}{% end %}
            </div>
        </div>
        <footer>
            {% block footer %}
                <p>
    For more information about our selection, hours or events, please email us at
    <a href="mailto:contact@burtsbooks.com">contact@burtsbooks.com</a>.
                </p>
            {% end %}
        </footer>
    </div>
    <script src="https://atts.w3cschool.cn/attachments/image/cimg/script.js") }}"></script>
    </body>
</html>

這個(gè)頁(yè)面定義了結(jié)構(gòu),應(yīng)用了一個(gè)CSS樣式表,并加載了主要的JavaScript文件。其他模板可以擴(kuò)展它,在必要時(shí)替換header、body和footer塊。

這個(gè)網(wǎng)站的index頁(yè)(index.html)歡迎友好的網(wǎng)站訪問者并提供一些商店的信息。通過(guò)擴(kuò)展main.html,這個(gè)文件只需要包括用于替換默認(rèn)文本的header和body塊的信息。

{% extends "main.html" %}

{% block header %}
    <h1>{{ header_text }}</h1>
{% end %}

{% block body %}
    <div id="hello">
        <p>Welcome to Burt's Books!</p>
        <p>...</p>
    </div>
{% end %}

在footer塊中,這個(gè)文件使用了Tornado模板的默認(rèn)行為,繼承了來(lái)自父模板的聯(lián)系信息。

為了運(yùn)作網(wǎng)站,傳遞信息給index模板,下面給出Burt's Book的Python腳本(main.py):

import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
import os.path

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/", MainHandler),
        ]
        settings = dict(
            template_path=os.path.join(os.path.dirname(__file__), "templates"),
            static_path=os.path.join(os.path.dirname(__file__), "static"),
            debug=True,
        )
        tornado.web.Application.__init__(self, handlers, **settings)

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render(
            "index.html",
            page_title = "Burt's Books | Home",
            header_text = "Welcome to Burt's Books!",
        )

if __name__ == "__main__":
    tornado.options.parse_command_line()
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

這個(gè)例子的結(jié)構(gòu)和我們之前見到的不太一樣,但你一點(diǎn)都不需要害怕。我們不再像之前那樣通過(guò)使用一個(gè)處理類列表和一些其他關(guān)鍵字參數(shù)調(diào)用tornado.web.Application的構(gòu)造函數(shù)來(lái)創(chuàng)建實(shí)例,而是定義了我們自己的Application子類,在這里我們簡(jiǎn)單地稱之為Application。在我們定義的init方法中,我們創(chuàng)建了處理類列表以及一個(gè)設(shè)置的字典,然后在初始化子類的調(diào)用中傳遞這些值,就像下面的代碼一樣:

tornado.web.Application.__init__(self, handlers, **settings)

所以在這個(gè)系統(tǒng)中,Burt's Book可以很容易地改變index頁(yè)面并保持基礎(chǔ)模板在其他頁(yè)面被使用時(shí)完好。此外,他們可以充分利用Tornado的真實(shí)能量,由Python腳本和/或數(shù)據(jù)庫(kù)提供動(dòng)態(tài)內(nèi)容。我們將在之后看到更多相關(guān)的內(nèi)容。

3.1.3 自動(dòng)轉(zhuǎn)義

Tornado默認(rèn)會(huì)自動(dòng)轉(zhuǎn)義模板中的內(nèi)容,把標(biāo)簽轉(zhuǎn)換為相應(yīng)的HTML實(shí)體。這樣可以防止后端為數(shù)據(jù)庫(kù)的網(wǎng)站被惡意腳本攻擊。比如,你的網(wǎng)站中有一個(gè)評(píng)論部分,用戶可以在這里添加任何他們想說(shuō)的文字進(jìn)行討論。雖然一些HTML標(biāo)簽在標(biāo)記和樣式?jīng)_突時(shí)不構(gòu)成重大威脅(如評(píng)論中沒有閉標(biāo)簽),但標(biāo)簽會(huì)允許攻擊者加載其他的JavaScript文件,打開通向跨站腳本攻擊、XSS或漏洞之門。

讓我們考慮Burt's Book網(wǎng)站上的一個(gè)用戶反饋?lái)?yè)面。Melvin,今天感覺特別邪惡,在評(píng)論里提交了下面的文字:

Totally hacked your site lulz <script>alert('RUNNING EVIL H4CKS AND SPL01TS NOW...')</script>

當(dāng)我們?cè)跊]有轉(zhuǎn)義用戶內(nèi)容的情況下給一個(gè)不知情的用戶構(gòu)建頁(yè)面時(shí),腳本標(biāo)簽被作為一個(gè)HTML元素解釋,并被瀏覽器執(zhí)行,所以Alice看到了如圖3-4所示的提示窗口。幸虧Tornado會(huì)自動(dòng)轉(zhuǎn)義在雙大括號(hào)間被渲染的表達(dá)式。更早地轉(zhuǎn)義Melvin輸入的文本不會(huì)激活HTML標(biāo)簽,并且會(huì)渲染為下面的字符串:

Totally hacked your site lulz &lt;script&gt;alert('RUNNING EVIL H4CKS AND SPL01TS NOW...')&lt;/script&gt;

圖3-4

圖3-4 網(wǎng)站漏洞問題

現(xiàn)在當(dāng)Alice訪問網(wǎng)站時(shí),沒有惡意腳本被執(zhí)行,所以她看到的頁(yè)面如圖3-5所示。

圖3-5

圖3-5 網(wǎng)站漏洞問題--解決

在Tornado1.x版本中,模板沒有被自動(dòng)轉(zhuǎn)義,所以我們之前談?wù)摰姆雷o(hù)措施需要顯式地在未過(guò)濾的用戶輸入上調(diào)用escape()函數(shù)。

所以在這里,我們可以看到自動(dòng)轉(zhuǎn)義是如何防止你的訪客進(jìn)行惡意攻擊的。然而,當(dāng)通過(guò)模板和模塊提供HTML動(dòng)態(tài)內(nèi)容時(shí)它仍會(huì)讓你措手不及。

舉個(gè)例子,如果Burt想在footer中使用模板變量設(shè)置email聯(lián)系鏈接,他將不會(huì)得到期望的HTML鏈接??紤]下面的模板片段:

{% set mailLink = "<a href="mailto:contact@burtsbooks.com">Contact Us</a>" %}
{{ mailLink }}'

它會(huì)在頁(yè)面源代碼中渲染成如下代碼:

&lt;a href=&quot;mailto:contact@burtsbooks.com&quot;&gt;Contact Us&lt;/a&gt;

此時(shí)自動(dòng)轉(zhuǎn)義被運(yùn)行了,很明顯,這無(wú)法讓人們聯(lián)系上Burt。

為了處理這種情況,你可以禁用自動(dòng)轉(zhuǎn)義,一種方法是在Application構(gòu)造函數(shù)中傳遞autoescape=None,另一種方法是在每頁(yè)的基礎(chǔ)上修改自動(dòng)轉(zhuǎn)義行為,如下所示:

{% autoescape None %}
{{ mailLink }}

這些autoescape塊不需要結(jié)束標(biāo)簽,并且可以設(shè)置xhtml_escape來(lái)開啟自動(dòng)轉(zhuǎn)義(默認(rèn)行為),或None來(lái)關(guān)閉。

然而,在理想的情況下,你希望保持自動(dòng)轉(zhuǎn)義開啟以便繼續(xù)防護(hù)你的網(wǎng)站。因此,你可以使用{% raw %}指令來(lái)輸出不轉(zhuǎn)義的內(nèi)容。

{% raw mailLink %}

需要特別注意的是,當(dāng)你使用諸如Tornado的linkify()和xsrf_form_html()函數(shù)時(shí),自動(dòng)轉(zhuǎn)義的設(shè)置被改變了。所以如果你希望在前面代碼的footer中使用linkify()來(lái)包含鏈接,你可以使用一個(gè){% raw %}塊:

{% block footer %}
    <p>
        For more information about our selection, hours or events, please email us at
        <a href="mailto:contact@burtsbooks.com">contact@burtsbooks.com</a>.
    </p>

    <p class="small">
        Follow us on Facebook at
        {% raw linkify("https://fb.me/burtsbooks", extra_params='ref=website') %}.
    </p>
{% end %}

這樣,你可以既利用linkify()簡(jiǎn)記的好處,又可以保持在其他地方自動(dòng)轉(zhuǎn)義的好處。

3.2 UI模塊

正如前面我們所看到的,模板系統(tǒng)既輕量級(jí)又強(qiáng)大。在實(shí)踐中,我們希望遵循軟件工程的諺語(yǔ),Don't Repeat Yourself。為了消除冗余的代碼,我們可以使模板部分模塊化。比如,展示物品列表的頁(yè)面可以定位一個(gè)單獨(dú)的模板用來(lái)渲染每個(gè)物品的標(biāo)記。另外,一組共用通用導(dǎo)航結(jié)構(gòu)的頁(yè)面可以從一個(gè)共享的模塊渲染內(nèi)容。Tornado的UI模塊在這種情況下特別有用

UI模塊是封裝模板中包含的標(biāo)記、樣式以及行為的可復(fù)用組件。它所定義的元素通常用于多個(gè)模板交叉復(fù)用或在同一個(gè)模板中重復(fù)使用。模塊本身是一個(gè)繼承自Tornado的UIModule類的簡(jiǎn)單Python類,并定義了一個(gè)render方法。當(dāng)一個(gè)模板使用{% module Foo(...) %}標(biāo)簽引用一個(gè)模塊時(shí),Tornado的模板引擎調(diào)用模塊的render方法,然后返回一個(gè)字符串來(lái)替換模板中的模塊標(biāo)簽。UI模塊也可以在渲染后的頁(yè)面中嵌入自己的JavaScript和CSS文件,或指定額外包含的JavaScript或CSS文件。你可以定義可選的embedded_javascript、embedded_css、javascript_files和css_files方法來(lái)實(shí)現(xiàn)這一方法。

3.2.1 基礎(chǔ)模塊使用

為了在你的模板中引用模塊,你必須在應(yīng)用的設(shè)置中聲明它。ui_moudles參數(shù)期望一個(gè)模塊名為鍵、類為值的字典輸入來(lái)渲染它們??紤]代碼清單3-1。

代碼清單3-1 模塊基礎(chǔ):hello_module.py

import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
import os.path

from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

class HelloHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('hello.html')

class HelloModule(tornado.web.UIModule):
    def render(self):
        return '<h1>Hello, world!</h1>'

if __name__ == '__main__':
    tornado.options.parse_command_line()
    app = tornado.web.Application(
        handlers=[(r'/', HelloHandler)],
        template_path=os.path.join(os.path.dirname(__file__), 'templates'),
        ui_modules={'Hello': HelloModule}
    )
    server = tornado.httpserver.HTTPServer(app)
    server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

這個(gè)例子中ui_module字典里只有一項(xiàng),它把到名為Hello的模塊的引用和我們定義的HelloModule類結(jié)合了起來(lái)。

現(xiàn)在,當(dāng)調(diào)用HelloHandler并渲染hello.html時(shí),我們可以使用{% module Hello() %}模板標(biāo)簽來(lái)包含HelloModule類中render方法返回的字符串。

<html>
    <head><title>UI Module Example</title></head>
    <body>
        {% module Hello() %}
    </body>
</html>

這個(gè)hello.html模板通過(guò)在模塊標(biāo)簽自身的位置調(diào)用HelloModule返回的字符串進(jìn)行填充。下一節(jié)的例子將會(huì)展示如何擴(kuò)展UI模塊來(lái)渲染它們自己的模板并包含腳本和樣式表。

3.2.2 模塊深入

很多時(shí)候,一個(gè)非常有用的做法是讓模塊指向一個(gè)模板文件而不是在模塊類中直接渲染字符串。這些模板的標(biāo)記看起來(lái)就像我們已經(jīng)看到過(guò)的作為整體的模板。

UI模塊的一個(gè)常見應(yīng)用是迭代數(shù)據(jù)庫(kù)或API查詢中獲得的結(jié)果,為每個(gè)獨(dú)立項(xiàng)目的數(shù)據(jù)渲染相同的標(biāo)記。比如,Burt想在Burt's Book里創(chuàng)建一個(gè)推薦閱讀部分,他已經(jīng)創(chuàng)建了一個(gè)名為recommended.html的模板,其代碼如下所示。就像前面看到的那樣,我們將使用{% module Book(book) %}標(biāo)簽調(diào)用模塊。

{% extends "main.html" %}

{% block body %}
<h2>Recommended Reading</h2>
    {% for book in books %}
        {% module Book(book) %}
    {% end %}
{% end %}

Burt還創(chuàng)建了一個(gè)叫作book.html的圖書模塊的模板,并把它放到了templates/modules目錄下。一個(gè)簡(jiǎn)單的圖書模板看起來(lái)像下面這樣:

<div class="book">
    <h3 class="book_title">{{ book["title"] }}</h3>
    <img src="{{ book["image"] }}" class="book_image"/>
</div>

現(xiàn)在,當(dāng)我們定義BookModule類的時(shí)候,我們將調(diào)用繼承自UIModule的render_string方法。這個(gè)方法顯式地渲染模板文件,當(dāng)我們返回給調(diào)用者時(shí)將其關(guān)鍵字參數(shù)作為一個(gè)字符串。

class BookModule(tornado.web.UIModule):
    def render(self, book):
        return self.render_string('modules/book.html', book=book)

在完整的例子中,我們將使用下面的模板來(lái)格式化每個(gè)推薦書籍的所有屬性,代替先前的book.html

<div class="book">
    <h3 class="book_title">{{ book["title"] }}</h3>
    {% if book["subtitle"] != "" %}
        <h4 class="book_subtitle">{{ book["subtitle"] }}</h4>
    {% end %}
    <img src="{{ book["image"] }}" class="book_image"/>
    <div class="book_details">
        <div class="book_date_released">Released: {{ book["date_released"]}}</div>
        <div class="book_date_added">
            Added: {{ locale.format_date(book["date_added"], relative=False) }}
        </div>
        <h5>Description:</h5>
        <div class="book_body">{% raw book["description"] %}</div>
    </div>
</div>

使用這個(gè)布局,傳遞給recommended.html模板的books參數(shù)的每項(xiàng)都將會(huì)調(diào)用這個(gè)模塊。每次使用一個(gè)新的book參數(shù)調(diào)用Book模塊時(shí),模塊(以及book.html模板)可以引用book參數(shù)的字典中的項(xiàng),并以適合的方式格式化數(shù)據(jù)(如圖3-6)。

圖3-6

圖3-6 包含樣式數(shù)據(jù)的圖書模塊

現(xiàn)在,我們可以定義一個(gè)RecommendedHandler類來(lái)渲染模板,就像你通常的操作那樣。這個(gè)模板可以在渲染推薦書籍列表時(shí)引用Book模塊。

class RecommendedHandler(tornado.web.RequestHandler):
    def get(self):
        self.render(
            "recommended.html",
            page_title="Burt's Books | Recommended Reading",
            header_text="Recommended Reading",
            books=[
                {
                    "title":"Programming Collective Intelligence",
                    "subtitle": "Building Smart Web 2.0 Applications",
                    "image":"/static/images/collective_intelligence.gif",
                    "author": "Toby Segaran",
                    "date_added":1310248056,
                    "date_released": "August 2007",
                    "isbn":"978-0-596-52932-1",
                    "description":"<p>This fascinating book demonstrates how you "
                        "can build web applications to mine the enormous amount of data created by people "
                        "on the Internet. With the sophisticated algorithms in this book, you can write "
                        "smart programs to access interesting datasets from other web sites, collect data "
                        "from users of your own applications, and analyze and understand the data once "
                        "you've found it.</p>"
                },
                ...
            ]
        )

如果要用更多的模塊,只需要簡(jiǎn)單地在ui_modules參數(shù)中添加映射值。因?yàn)槟0蹇梢灾赶蛉魏味x在ui_modules字典中的模塊,所以在自己的模塊中指定功能非常容易。

在這個(gè)例子中,你可能已經(jīng)注意到了locale.format_date()的使用。它調(diào)用了tornado.locale模塊提供的日期處理方法,這個(gè)模塊本身是一組i18n方法的集合。format_date()選項(xiàng)默認(rèn)格式化GMT Unix時(shí)間戳為XX time ago,并且可以向下面這樣使用:

{{ locale.format_date(book["date"]) }}

relative=False將使其返回一個(gè)絕對(duì)時(shí)間(包含小時(shí)和分鐘),而full_format=True選項(xiàng)將會(huì)展示一個(gè)包含月、日、年和時(shí)間的完整日期(比如,July 9, 2011 at 9:47 pm),當(dāng)搭配shorter=True使用時(shí)可以隱藏時(shí)間,只顯示月、日和年。

這個(gè)模塊在你處理時(shí)間和日期時(shí)非常有用,并且還提供了處理本地化字符串的支持。

3.2.3 嵌入JavaScript和CSS

為了給這些模塊提供更高的靈活性,Tornado允許你使用embedded_css和embedded_javascript方法嵌入其他的CSS和JavaScript文件。舉個(gè)例子,如果你想在調(diào)用模塊時(shí)給DOM添加一行文字,你可以通過(guò)從模塊中嵌入JavaScript來(lái)做到:

class BookModule(tornado.web.UIModule):
    def render(self, book):
        return self.render_string(
            "modules/book.html",
            book=book,
        )

    def embedded_javascript(self):
        return "document.write(\"hi!\")"

當(dāng)調(diào)用模塊時(shí),document.write(\"hi!\")將被包圍,并被插入到的閉標(biāo)簽中:

<script type="text/javascript">
//<![CDATA[
document.write("hi!")
//]]>
</script>

顯然,只是在文檔主體中寫這些內(nèi)容并不是世界上最有用的事情,而我們還有另一個(gè)給予你極大靈活性的選項(xiàng),當(dāng)創(chuàng)建這些模塊時(shí),可以在每個(gè)模塊中包含JavaScript文件。

類似的,你也可以把只在這些模塊被調(diào)用時(shí)加載的額外的CSS規(guī)則放進(jìn)來(lái):

def embedded_css(self):
    return ".book {background-color:#F5F5F5}"

在這種情況下,.book {background-color:#555}這條CSS規(guī)則被包裹在中,并被直接添加到的閉標(biāo)簽之前。

<style type="text/css">
.book {background-color:#F5F5F5}
</style>

更加靈活的是,你甚至可以簡(jiǎn)單地使用html_body()來(lái)在閉合的標(biāo)簽前添加完整的HTML標(biāo)記:

def html_body(self):
    return "<script>document.write("Hello!")</script>"

顯然,雖然直接內(nèi)嵌添加腳本和樣式表很有用,但是為了更嚴(yán)謹(jǐn)?shù)陌ㄒ约案麧嵉拇a?。砑訕邮奖砗湍_本文件會(huì)顯得更好。他們的工作方式基本相同,所以你可以使用javascript_files()和css_files()來(lái)包含完整的文件,不論是本地的還是外部的。

比如,你可以添加一個(gè)額外的本地CSS文件如下:

def css_files(self):
    return "/static/css/newreleases.css"

或者你可以取得一個(gè)外部的JavaScript文件:

def javascript_files(self):
    return "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"

當(dāng)一個(gè)模塊需要額外的庫(kù)而應(yīng)用的其他地方不是必需的時(shí)候,這種方式非常有用。比如,你有一個(gè)使用JQuery UI庫(kù)的模塊(而在應(yīng)用的其他地方都不會(huì)被使用),你可以只在這個(gè)樣本模塊中加載jquery-ui.min.js文件,減少那些不需要它的頁(yè)面的加載時(shí)間。

因?yàn)槟K的內(nèi)嵌JavaScript和內(nèi)嵌HTML函數(shù)的目標(biāo)都是緊鄰標(biāo)簽,html_body()、javascript_files()和embedded_javascript()都會(huì)將內(nèi)容渲染后插到頁(yè)面底部,那么它們出現(xiàn)的順序正好是你指定它們的順序的倒序。

如果你有一個(gè)模塊如下面的代碼所示:

class SampleModule(tornado.web.UIModule):
    def render(self, sample):
        return self.render_string(
            "modules/sample.html",
            sample=sample
        )

    def html_body(self):
        return "<div class=\"addition\"><p>html_body()</p></div>"

    def embedded_javascript(self):
        return "document.write(\"<p>embedded_javascript()</p>\")"

    def embedded_css(self):
        return ".addition {color: #A1CAF1}"

    def css_files(self):
        return "/static/css/sample.css"

    def javascript_files(self):
        return "/static/js/sample.js"

html_body()最先被編寫,它緊挨著出現(xiàn)在標(biāo)簽的上面。embedded_javascript()接著被渲染,最后是javascript_files()。你可以在圖3-7中看到它是如何工作的。

需要小心的是,你不能包括一個(gè)需要其他地方東西的方法(比如依賴其他文件的JavaScript函數(shù)),因?yàn)榇藭r(shí)他們可能會(huì)按照和你期望不同的順序進(jìn)行渲染。

總之,模塊允許你在模板中渲染格式化數(shù)據(jù)時(shí)非常靈活,同時(shí)也讓你能夠只在調(diào)用模塊時(shí)包含指定的一些額外的樣式和函數(shù)規(guī)則。

3.3 總結(jié)

正如我們之前看到的,Tornado使擴(kuò)展模板更容易,以便你的網(wǎng)站代碼可以在整個(gè)應(yīng)用中輕松復(fù)用。而使用模塊后,你可以在什么文件、樣式和腳本動(dòng)作需要被包括進(jìn)來(lái)這個(gè)問題上擁有更細(xì)粒度的決策。然而,我們的例子依賴于使用Python原生數(shù)據(jù)結(jié)構(gòu)時(shí)是否簡(jiǎn)單,在你的實(shí)際應(yīng)用中硬編碼大數(shù)據(jù)結(jié)構(gòu)的感覺可不好。下一步,我們將看到如何配合持久化存儲(chǔ)來(lái)處理存儲(chǔ)、提供和編輯動(dòng)態(tài)內(nèi)容。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)