使用 Bootstrap 实现导航非常简单。鉴于前台界面的导航并不在 Flask 技术体系中,所以本文不打算对 Bootstrap 的细节进行探讨,仅演示基本要点。大家可自行参考下面的文章:
比如,一个最简单的 Flask 程序中,下面的代码就实现了一个漂亮的导航栏:
<html>
<head>
<title>Bootstrap 导航title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js">script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js">script>
head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"
aria-expanded="false">
<span class="sr-only">Toggle navigationspan>
<span class="icon-bar">span>
<span class="icon-bar">span>
<span class="icon-bar">span>
button>
<a class="navbar-brand">
<img style="max-width:30px;margin-top:-6px;" class="logo" src="{{ url_for('static',filename='images/logo.jpg') }}">
a>
div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active">
<a href="#">首页
<span class="sr-only">(current)span>
a>
li>
<li>
<a href="#">发布问答a>
li>
ul>
<form class="navbar-form navbar-left">
<div class="form-group">
<input type="text" class="form-control" placeholder="Key Words">
div>
<button type="submit" class="btn btn-default">搜索button>
form>
<ul class="nav navbar-nav navbar-right">
<li>
<a href="#">登录a>
li>
<li>
<a href="#">注册a>
li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">友情链接
<span class="caret">span>
a>
<ul class="dropdown-menu">
<li>
<a href="mailto:[email protected]">联系我a>
li>
<li>
<a href="http://flask.pocoo.org" target="_blank">Flask官网a>
li>
<li>
<a href="https://www.python.org/">Python官网a>
li>
<li role="separator" class="divider">li>
<li role="separator" class="divider">li>
<li>
<a href="https://www.google.com.hk" target="_blank">Google Searcha>
li>
ul>
li>
ul>
div>
div>
nav>
body>
html>
flask 插件 flask-nav 实现了使用 python 语言来操作 html 标签,并且实现一个简单的渲染器对这些标签进行渲染。flask-bootstrap 也支持 flask-nav, 并且实现了一个渲染器 – BootstrapRenderer。下面说说 flask-nav 的用法。
安装:pip install flask-nav
使用 flask-nav 需要 4 步:
# 导入 模块
from flask_nav import Nav
from flask_nav.elements import *
# 1. 实例化 Nav
nav = Nav()
# 2. 注册 navigation item
nav.register_element('top', Navbar(
View('Home.', 'index'),
Subgroup(
'Products',
View('Wg240-Series', 'products', product='wg240'),
View('Wg250-Series', 'products', product='wg250'),
Separator(),
Text('Discontinued Products'),
View('Wg10X', 'products', product='wg10x'),
),
Link('Tech Support', 'http://techsupport.invalid/widgits_inc'),
View('About', 'about')
))
# 3. 初始化 app
nav.init_app(app)
第 4 步:在 html 文件中渲染 html tag:
{{nav.top.render()}}
我们先来看看效果, 先给出完整代码:
# app.py
from flask import Flask, render_template
from flask_nav import Nav
from flask_nav.elements import *
app = Flask(__name__)
nav = Nav()
# registers the "top" menubar
nav.register_element('top', Navbar(
View('Home', 'index'),
Subgroup(
'Products',
View('Wg240-Series', 'products', product='wg240'),
View('Wg250-Series', 'products', product='wg250'),
Separator(),
Text('Discontinued Products'),
View('Wg10X', 'products', product='wg10x'),
),
Link('Tech Support', 'http://techsupport.invalid/widgits_inc'),
View('About', 'about')
))
nav.init_app(app)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/about')
def about():
return 'This is the ABOUT page'
@app.route('/products/' )
def products(product):
return 'product: ' + str(product)
if __name__ == '__main__':
app.run()
index.html 文件:
<html>
<head>
<title>
Flask navigation using flask-nav
title>
head>
<body>
{{nav.top.render()}}
body>
html>
运行 Flask app的界面效果如下:
如果查看一下网页的源码,我们发现无非就是使用 html 标签 ul
和 li
进行标记的,加上了 class 属性。
<nav class="navbar">
<ul>
<li>
<div>
<span>Productsspan>
<ul class="subgroup">
<li>
<a href="/products/wg240" title="Wg240-Series">Wg240-Seriesa>
li>
<li>
<a href="/products/wg250" title="Wg250-Series">Wg250-Seriesa>
li>
<li>
<hr class="separator">
li>
<li>
<span class="nav-label">Discontinued Productsspan>
li>
<li>
<a href="/products/wg10x" title="Wg10X">Wg10Xa>
li>
ul>
div>
li>
<li>
<a href="http://techsupport.invalid/widgits_inc">Tech Supporta>
li>
<li>
<a href="/about" title="About">Abouta>
li>
ul>
nav>
为了能理解 flask-nav 的机制,讲解一下重要的知识点。首先是 flask-nav 的 NavigationItem:
这些元素都是使用 Python 的类来定义的,class diagram 如下:
在 flask-nav 提供的渲染器中,这些 item 是这样渲染成 tag 的:
class SimpleRenderer(Renderer):
def __init__(self, **kwargs):
self.kwargs = kwargs
def visit_Link(self, node):
return tags.a(node.text, href=node.get_url())
def visit_Navbar(self, node):
kwargs = {'_class': 'navbar'}
kwargs.update(self.kwargs)
cont = tags.nav(**kwargs)
ul = cont.add(tags.ul())
for item in node.items:
ul.add(tags.li(self.visit(item)))
return cont
def visit_View(self, node):
kwargs = {}
if node.active:
kwargs['_class'] = 'active'
return tags.a(node.text,
href=node.get_url(),
title=node.text,
**kwargs)
def visit_Subgroup(self, node):
group = tags.ul(_class='subgroup')
title = tags.span(node.title)
if node.active:
title.attributes['class'] = 'active'
for item in node.items:
group.add(tags.li(self.visit(item)))
return tags.div(title, group)
def visit_Separator(self, node):
return tags.hr(_class='separator')
def visit_Text(self, node):
return tags.span(node.text, _class='nav-label')
下面我们对代码稍作改变,在 app.py
中加入 flask-bootstrap 内容:
from flask_bootstrap import Bootstrap
app = Flask(__name__)
Bootstrap(app)
然后将 index.html 从 Bootstrap/base.html 继承:
{% extends 'bootstrap/base.html' %}
{% block content %}
{{nav.top.render()}}
{% endblock %}
再次运行 app,index.html 页面的效果变成了很漂亮的导航条:
flask-bootstrap 使用了自定义的 BootstrapRenderer
进行渲染。以下是 BootstrapRenderer
的主要代码:
class BootstrapRenderer(Visitor):
def __init__(self, html5=True, id=None):
self.html5 = html5
self._in_dropdown = False
self.id = id
def visit_Navbar(self, node):
# create a navbar id that is somewhat fixed, but do not leak any
# information about memory contents to the outside
node_id = self.id or sha1(str(id(node)).encode()).hexdigest()
root = tags.nav() if self.html5 else tags.div(role='navigation')
root['class'] = 'navbar navbar-default'
cont = root.add(tags.div(_class='container-fluid'))
# collapse button
header = cont.add(tags.div(_class='navbar-header'))
btn = header.add(tags.button())
btn['type'] = 'button'
btn['class'] = 'navbar-toggle collapsed'
btn['data-toggle'] = 'collapse'
btn['data-target'] = '#' + node_id
btn['aria-expanded'] = 'false'
btn['aria-controls'] = 'navbar'
btn.add(tags.span('Toggle navigation', _class='sr-only'))
btn.add(tags.span(_class='icon-bar'))
btn.add(tags.span(_class='icon-bar'))
btn.add(tags.span(_class='icon-bar'))
# title may also have a 'get_url()' method, in which case we render
# a brand-link
if node.title is not None:
if hasattr(node.title, 'get_url'):
header.add(tags.a(node.title.text, _class='navbar-brand',
href=node.title.get_url()))
else:
header.add(tags.span(node.title, _class='navbar-brand'))
bar = cont.add(tags.div(
_class='navbar-collapse collapse',
id=node_id,
))
bar_list = bar.add(tags.ul(_class='nav navbar-nav'))
for item in node.items:
bar_list.add(self.visit(item))
return root
def visit_Text(self, node):
if not self._in_dropdown:
return tags.p(node.text, _class='navbar-text')
return tags.li(node.text, _class='dropdown-header')
def visit_Link(self, node):
item = tags.li()
item.add(tags.a(node.text, href=node.get_url()))
return item
def visit_Separator(self, node):
if not self._in_dropdown:
raise RuntimeError('Cannot render separator outside Subgroup.')
return tags.li(role='separator', _class='divider')
def visit_Subgroup(self, node):
if not self._in_dropdown:
li = tags.li(_class='dropdown')
if node.active:
li['class'] = 'active'
a = li.add(tags.a(node.title, href='#', _class='dropdown-toggle'))
a['data-toggle'] = 'dropdown'
a['role'] = 'button'
a['aria-haspopup'] = 'true'
a['aria-expanded'] = 'false'
a.add(tags.span(_class='caret'))
ul = li.add(tags.ul(_class='dropdown-menu'))
self._in_dropdown = True
for item in node.items:
ul.add(self.visit(item))
self._in_dropdown = False
return li
else:
raise RuntimeError('Cannot render nested Subgroups')
def visit_View(self, node):
item = tags.li()
item.add(tags.a(node.text, href=node.get_url(), title=node.text))
if node.active:
item['class'] = 'active'
return item
在 init_app()
中将 flask-nav 替换成 BootsrapRenderer:
# setup support for flask-nav
renderers = app.extensions.setdefault('nav_renderers', {})
renderer_name = (__name__ + '.nav', 'BootstrapRenderer')
renderers['bootstrap'] = renderer_name
# make bootstrap the default renderer
renderers[None] = renderer_name