# Day 8 - 构建前端
虽然我们跑通了一个最简单的MVC,但是页面效果肯定不会让人满意。
对于复杂的HTML前端页面来说,我们需要一套基础的CSS框架来完成页面布局和基本样式。另外,jQuery作为操作DOM的JavaScript库也必不可少。
从零开始写CSS不如直接从一个已有的功能完善的CSS框架开始。有很多CSS框架可供选择。我们这次选择[uikit](http://getuikit.com/)这个强大的CSS框架。它具备完善的响应式布局,漂亮的UI,以及丰富的HTML组件,让我们能轻松设计出美观而简洁的页面。
可以从[uikit首页](http://getuikit.com/)下载打包的资源文件。
所有的静态资源文件我们统一放到`www/static`目录下,并按照类别归类:
```
static/
+- css/
| +- addons/
| | +- uikit.addons.min.css
| | +- uikit.almost-flat.addons.min.css
| | +- uikit.gradient.addons.min.css
| +- awesome.css
| +- uikit.almost-flat.addons.min.css
| +- uikit.gradient.addons.min.css
| +- uikit.min.css
+- fonts/
| +- fontawesome-webfont.eot
| +- fontawesome-webfont.ttf
| +- fontawesome-webfont.woff
| +- FontAwesome.otf
+- js/
+- awesome.js
+- html5.js
+- jquery.min.js
+- uikit.min.js
```
由于前端页面肯定不止首页一个页面,每个页面都有相同的页眉和页脚。如果每个页面都是独立的HTML模板,那么我们在修改页眉和页脚的时候,就需要把每个模板都改一遍,这显然是没有效率的。
常见的模板引擎已经考虑到了页面上重复的HTML部分的复用问题。有的模板通过include把页面拆成三部分:
```
<html>
<% include file="inc_header.html" %>
<% include file="index_body.html" %>
<% include file="inc_footer.html" %>
</html>
```
这样,相同的部分`inc_header.html`和`inc_footer.html`就可以共享。
但是include方法不利于页面整体结构的维护。jinjia2的模板还有另一种“继承”方式,实现模板的复用更简单。
“继承”模板的方式是通过编写一个“父模板”,在父模板中定义一些可替换的block(块)。然后,编写多个“子模板”,每个子模板都可以只替换父模板定义的block。比如,定义一个最简单的父模板:
```
<!-- base.html -->
<html>
<head>
<title>{% block title%} 这里定义了一个名为title的block {% endblock %}</title>
</head>
<body>
{% block content %} 这里定义了一个名为content的block {% endblock %}
</body>
</html>
```
对于子模板`a.html`,只需要把父模板的`title`和`content`替换掉:
```
{% extends 'base.html' %}
{% block title %} A {% endblock %}
{% block content %}
<h1>Chapter A</h1>
<p>blablabla...</p>
{% endblock %}
```
对于子模板`b.html`,如法炮制:
```
{% extends 'base.html' %}
{% block title %} B {% endblock %}
{% block content %}
<h1>Chapter B</h1>
<ul>
<li>list 1</li>
<li>list 2</li>
</ul>
{% endblock %}
```
这样,一旦定义好父模板的整体布局和CSS样式,编写子模板就会非常容易。
让我们通过uikit这个CSS框架来完成父模板`__base__.html`的编写:
```
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
{% block meta %}<!-- block meta -->{% endblock %}
<title>{% block title %} ? {% endblock %} - Awesome Python Webapp</title>
<link rel="stylesheet" href="/static/css/uikit.min.css">
<link rel="stylesheet" href="/static/css/uikit.gradient.min.css">
<link rel="stylesheet" href="/static/css/awesome.css" />
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/md5.js"></script>
<script src="/static/js/uikit.min.js"></script>
<script src="/static/js/awesome.js"></script>
{% block beforehead %}<!-- before head -->{% endblock %}
</head>
<body>
<nav class="uk-navbar uk-navbar-attached uk-margin-bottom">
<div class="uk-container uk-container-center">
<a href="/" class="uk-navbar-brand">Awesome</a>
<ul class="uk-navbar-nav">
<li data-url="blogs"><a href="/"><i class="uk-icon-home"></i> 日志</a></li>
<li><a target="_blank" href="#"><i class="uk-icon-book"></i> 教程</a></li>
<li><a target="_blank" href="#"><i class="uk-icon-code"></i> 源码</a></li>
</ul>
<div class="uk-navbar-flip">
<ul class="uk-navbar-nav">
{% if user %}
<li class="uk-parent" data-uk-dropdown>
<a href="#0"><i class="uk-icon-user"></i> {{ user.name }}</a>
<div class="uk-dropdown uk-dropdown-navbar">
<ul class="uk-nav uk-nav-navbar">
<li><a href="/signout"><i class="uk-icon-sign-out"></i> 登出</a></li>
</ul>
</div>
</li>
{% else %}
<li><a href="/signin"><i class="uk-icon-sign-in"></i> 登陆</a></li>
<li><a href="/register"><i class="uk-icon-edit"></i> 注册</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="uk-container uk-container-center">
<div class="uk-grid">
<!-- content -->
{% block content %}
{% endblock %}
<!-- // content -->
</div>
</div>
<div class="uk-margin-large-top" style="background-color:#eee; border-top:1px solid #ccc;">
<div class="uk-container uk-container-center uk-text-center">
<div class="uk-panel uk-margin-top uk-margin-bottom">
<p>
<a target="_blank" href="#" class="uk-icon-button uk-icon-weibo"></a>
<a target="_blank" href="#" class="uk-icon-button uk-icon-github"></a>
<a target="_blank" href="#" class="uk-icon-button uk-icon-linkedin-square"></a>
<a target="_blank" href="#" class="uk-icon-button uk-icon-twitter"></a>
</p>
<p>Powered by <a href="#">Awesome Python Webapp</a>. Copyright © 2014\. [<a href="/manage/" target="_blank">Manage</a>]</p>
<p><a href="http://www.liaoxuefeng.com/" target="_blank">www.liaoxuefeng.com</a>. All rights reserved.</p>
<a target="_blank" href="#"><i class="uk-icon-html5" style="font-size:64px; color: #444;"></i></a>
</div>
</div>
</div>
</body>
</html>
```
`__base__.html`定义的几个block作用如下:
用于子页面定义一些meta,例如rss feed:
```
{% block meta %} ... {% endblock %}
```
覆盖页面的标题:
```
{% block title %} ... {% endblock %}
```
子页面可以在`<head>`标签关闭前插入JavaScript代码:
```
{% block beforehead %} ... {% endblock %}
```
子页面的content布局和内容:
```
{% block content %}
...
{% endblock %}
```
我们把首页改造一下,从`__base__.html`继承一个`blogs.html`:
```
{% extends '__base__.html' %}
{% block title %}日志{% endblock %}
{% block content %}
<div class="uk-width-medium-3-4">
{% for blog in blogs %}
<article class="uk-article">
<h2><a href="/blog/{{ blog.id }}">{{ blog.name }}</a></h2>
<p class="uk-article-meta">发表于{{ blog.created_at}}</p>
<p>{{ blog.summary }}</p>
<p><a href="/blog/{{ blog.id }}">继续阅读 <i class="uk-icon-angle-double-right"></i></a></p>
</article>
<hr class="uk-article-divider">
{% endfor %}
</div>
<div class="uk-width-medium-1-4">
<div class="uk-panel uk-panel-header">
<h3 class="uk-panel-title">友情链接</h3>
<ul class="uk-list uk-list-line">
<li><i class="uk-icon-thumbs-o-up"></i> <a target="_blank" href="#">编程</a></li>
<li><i class="uk-icon-thumbs-o-up"></i> <a target="_blank" href="#">读书</a></li>
<li><i class="uk-icon-thumbs-o-up"></i> <a target="_blank" href="#">Python教程</a></li>
<li><i class="uk-icon-thumbs-o-up"></i> <a target="_blank" href="#">Git教程</a></li>
</ul>
</div>
</div>
{% endblock %}
```
相应地,首页URL的处理函数更新如下:
```
@get('/')
def index(request):
summary = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
blogs = [
Blog(id='1', name='Test Blog', summary=summary, created_at=time.time()-120),
Blog(id='2', name='Something New', summary=summary, created_at=time.time()-3600),
Blog(id='3', name='Learn Swift', summary=summary, created_at=time.time()-7200)
]
return {
'__template__': 'blogs.html',
'blogs': blogs
}
```
Blog的创建日期显示的是一个浮点数,因为它是由这段模板渲染出来的:
```
<p class="uk-article-meta">发表于{{ blog.created_at }}</p>
```
解决方法是通过jinja2的filter(过滤器),把一个浮点数转换成日期字符串。我们来编写一个`datetime`的filter,在模板里用法如下:
```
<p class="uk-article-meta">发表于{{ blog.created_at|datetime }}</p>
```
filter需要在初始化jinja2时设置。相关代码如下:
```
def datetime_filter(t):
delta = int(time.time() - t)
if delta < 60:
return '1分钟前'
if delta < 3600:
return '%s分钟前' % (delta // 60)
if delta < 86400:
return '%s小时前' % (delta // 3600)
if delta < 604800:
return '%s天前' % (delta // 86400)
dt = datetime.fromtimestamp(t)
return '%s年%s月%s日' % (dt.year, dt.month, dt.day)
...
init_jinja2(app, filters=dict(datetime=datetime_filter))
...
```
现在,完善的首页显示如下:
![home-with-uikit](https://box.kancloud.cn/2016-01-15_56988a2adba94.png)
## 参考源码
[day-08](https://github.com/michaelliao/awesome-python3-webapp/tree/day-08)
- JavaScript教程
- JavaScript简介
- 快速入门
- 基本语法
- 数据类型和变量
- 字符串
- 数组
- 对象
- 条件判断
- 循环
- Map和Set
- iterable
- 函数
- 函数定义和调用
- 变量作用域
- 方法
- 高阶函数
- map/reduce
- filter
- sort
- 闭包
- 箭头函数
- generator
- 标准对象
- Date
- RegExp
- JSON
- 面向对象编程
- 创建对象
- 原型继承
- 浏览器
- 浏览器对象
- 操作DOM
- 更新DOM
- 插入DOM
- 删除DOM
- 操作表单
- 操作文件
- AJAX
- Promise
- Canvas
- jQuery
- 选择器
- 层级选择器
- 查找和过滤
- 操作DOM
- 修改DOM结构
- 事件
- 动画
- 扩展
- underscore
- Collections
- Arrays
- Functions
- Objects
- Chaining
- Node.js
- 安装Node.js和npm
- 第一个Node程序
- 模块
- 基本模块
- fs
- stream
- http
- buffer
- Web开发
- koa
- mysql
- swig
- 自动化工具
- 期末总结
- Python 2.7教程
- Python简介
- 安装Python
- Python解释器
- 第一个Python程序
- 使用文本编辑器
- 输入和输出
- Python基础
- 数据类型和变量
- 字符串和编码
- 使用list和tuple
- 条件判断和循环
- 使用dict和set
- 函数
- 调用函数
- 定义函数
- 函数的参数
- 递归函数
- 高级特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 函数式编程
- 高阶函数
- map/reduce
- filter
- sorted
- 返回函数
- 匿名函数
- 装饰器
- 偏函数
- 模块
- 使用模块
- 安装第三方模块
- 使用__future__
- 面向对象编程
- 类和实例
- 访问限制
- 继承和多态
- 获取对象信息
- 面向对象高级编程
- 使用__slots__
- 使用@property
- 多重继承
- 定制类
- 使用元类
- 错误、调试和测试
- 错误处理
- 调试
- 单元测试
- 文档测试
- IO编程
- 文件读写
- 操作文件和目录
- 序列化
- 进程和线程
- 多进程
- 多线程
- ThreadLocal
- 进程 vs. 线程
- 分布式进程
- 正则表达式
- 常用内建模块
- collections
- base64
- struct
- hashlib
- itertools
- XML
- HTMLParser
- 常用第三方模块
- PIL
- 图形界面
- 网络编程
- TCP/IP简介
- TCP编程
- UDP编程
- 电子邮件
- SMTP发送邮件
- POP3收取邮件
- 访问数据库
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web开发
- HTTP协议简介
- HTML简介
- WSGI接口
- 使用Web框架
- 使用模板
- 协程
- gevent
- 实战
- Day 1 - 搭建开发环境
- Day 2 - 编写数据库模块
- Day 3 - 编写ORM
- Day 4 - 编写Model
- Day 5 - 编写Web框架
- Day 6 - 添加配置文件
- Day 7 - 编写MVC
- Day 8 - 构建前端
- Day 9 - 编写API
- Day 10 - 用户注册和登录
- Day 11 - 编写日志创建页
- Day 12 - 编写日志列表页
- Day 13 - 提升开发效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 编写移动App
- 期末总结
- Python3教程
- Python简介
- 安装Python
- Python解释器
- 第一个Python程序
- 使用文本编辑器
- Python代码运行助手
- 输入和输出
- Python基础
- 数据类型和变量
- 字符串和编码
- 使用list和tuple
- 条件判断
- 循环
- 使用dict和set
- 函数
- 调用函数
- 定义函数
- 函数的参数
- 递归函数
- 高级特性
- 切片
- 迭代
- 列表生成式
- 生成器
- 迭代器
- 函数式编程
- 高阶函数
- map/reduce
- filter
- sorted
- 返回函数
- 匿名函数
- 装饰器
- 偏函数
- 模块
- 使用模块
- 安装第三方模块
- 面向对象编程
- 类和实例
- 访问限制
- 继承和多态
- 获取对象信息
- 实例属性和类属性
- 面向对象高级编程
- 使用__slots__
- 使用@property
- 多重继承
- 定制类
- 使用枚举类
- 使用元类
- 错误、调试和测试
- 错误处理
- 调试
- 单元测试
- 文档测试
- IO编程
- 文件读写
- StringIO和BytesIO
- 操作文件和目录
- 序列化
- 进程和线程
- 多进程
- 多线程
- ThreadLocal
- 进程 vs. 线程
- 分布式进程
- 正则表达式
- 常用内建模块
- datetime
- collections
- base64
- struct
- hashlib
- itertools
- XML
- HTMLParser
- urllib
- 常用第三方模块
- PIL
- virtualenv
- 图形界面
- 网络编程
- TCP/IP简介
- TCP编程
- UDP编程
- 电子邮件
- SMTP发送邮件
- POP3收取邮件
- 访问数据库
- 使用SQLite
- 使用MySQL
- 使用SQLAlchemy
- Web开发
- HTTP协议简介
- HTML简介
- WSGI接口
- 使用Web框架
- 使用模板
- 异步IO
- 协程
- asyncio
- async/await
- aiohttp
- 实战
- Day 1 - 搭建开发环境
- Day 2 - 编写Web App骨架
- Day 3 - 编写ORM
- Day 4 - 编写Model
- Day 5 - 编写Web框架
- Day 6 - 编写配置文件
- Day 7 - 编写MVC
- Day 8 - 构建前端
- Day 9 - 编写API
- Day 10 - 用户注册和登录
- Day 11 - 编写日志创建页
- Day 12 - 编写日志列表页
- Day 13 - 提升开发效率
- Day 14 - 完成Web App
- Day 15 - 部署Web App
- Day 16 - 编写移动App
- FAQ
- 期末总结
- Git教程
- Git简介
- Git的诞生
- 集中式vs分布式
- 安装Git
- 创建版本库
- 时光机穿梭
- 版本回退
- 工作区和暂存区
- 管理修改
- 撤销修改
- 删除文件
- 远程仓库
- 添加远程库
- 从远程库克隆
- 分支管理
- 创建与合并分支
- 解决冲突
- 分支管理策略
- Bug分支
- Feature分支
- 多人协作
- 标签管理
- 创建标签
- 操作标签
- 使用GitHub
- 自定义Git
- 忽略特殊文件
- 配置别名
- 搭建Git服务器
- 期末总结