Flask 实现导航栏

使用 Bootstrap

使用 Bootstrap 实现导航非常简单。鉴于前台界面的导航并不在 Flask 技术体系中,所以本文不打算对 Bootstrap 的细节进行探讨,仅演示基本要点。大家可自行参考下面的文章:

比如,一个最简单的 Flask 程序中,下面的代码就实现了一个漂亮的导航栏:

<!DOCTYPE html>
<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 navigation</span>
                    <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>

            <!-- Collect the nav links, forms, and other content for toggling -->
            <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:dummy@qq.com">联系我</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 Search</a>
                            </li>
                        </ul>
                    </li>
                </ul>
            </div>
            <!-- /.navbar-collapse -->
        </div>
        <!-- /.container-fluid -->
    </nav>

</body>

</html>

使用 flask-nav

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/<product>')
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 标签 ulli 进行标记的,加上了 class 属性。

<nav class="navbar">
  <ul>
    <li>
      <div>
        <span>Products</span>
        <ul class="subgroup">
          <li>
            <a href="/products/wg240" title="Wg240-Series">Wg240-Series</a>
          </li>
          <li>
            <a href="/products/wg250" title="Wg250-Series">Wg250-Series</a>
          </li>
          <li>
            <hr class="separator">
          </li>
          <li>
            <span class="nav-label">Discontinued Products</span>
          </li>
          <li>
            <a href="/products/wg10x" title="Wg10X">Wg10X</a>
          </li>
        </ul>
      </div>
    </li>
    <li>
      <a href="http://techsupport.invalid/widgits_inc">Tech Support</a>
    </li>
    <li>
      <a href="/about" title="About">About</a>
    </li>
  </ul>
</nav>

为了能理解 flask-nav 的机制,讲解一下重要的知识点。首先是 flask-nav 的 NavigationItem:

  • Text: 对应 html 标签 span
  • Seperator: 对应 html 标签 hr
  • View: 对应到 html 超链接,需要 Flask 提供视图函数
  • Link: 对应到普通的 html 超链接
  • Subgroup: 用于对 Navigation item 进行分组
  • Navbar: 通常作为顶层 item

这些元素都是使用 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')

flask-bootstrap 对 flask-nav 的支持

下面我们对代码稍作改变,在 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

参考文档

本文源代码

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值