- 移动单页面应用
- 移动设备处理
- 前后端分离
- Riot.js
- ReactiveJS构建服务
- 创建博客列表页
- 博客详情页
- 添加导航
移动单页面应用
为了实现在移动设备上的访问,这里就以riot.js为例做一个简单的Demo。不过,首先我们需要在后台判断用户是来自于某种设备,再对其进行特殊的处理。
移动设备处理
幸运的是我们又找到了一个库名为django_mobile,可以根据用户的User-Agent来区别设备,并为其分配一个移动设备专用的模板。因此,我们需要安装这个库:
pip install django_mobile
并将'django_mobile.middleware.MobileDetectionMiddleware'和'django_mobile.middleware.SetFlavourMiddleware'添加MIDDLEWARE_CLASSES中:
MIDDLEWARE_CLASSES = ('django.contrib.sessions.middleware.SessionMiddleware','corsheaders.middleware.CorsMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.auth.middleware.SessionAuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware','django.contrib.flatpages.middleware.FlatpageFallbackMiddleware','django_mobile.middleware.MobileDetectionMiddleware','django_mobile.middleware.SetFlavourMiddleware')
修改Template配置,添加对应的loader和context_processor,如下所示的内容即是修改完后的结果:
TEMPLATES = [{'BACKEND': 'django.template.backends.django.DjangoTemplates','DIRS': ['templates/'],'OPTIONS': {'context_processors': ['django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages','django_mobile.context_processors.flavour'],},},]# dirty fixed for https://github.com/gregmuellegger/django-mobile/issues/72TEMPLATE_LOADERS = TEMPLATES[0]['OPTIONS']['loaders']
我们在LOADERS中添加了'django_mobile.loader.Loader',在context_processors中添加了django_mobile.context_processors.flavour。
然后在template目录中创建template/mobile/index.html文件,即可。
前后端分离
为了方便我们讲述模块化,也不改变系统原有架构,我决定挖个大坑使用Riot.js来展示这一部分的内容。
Riot.js
Riot拥有创建现代客户端应用的所有必需的成分:
- “响应式” 视图层用来创建用户界面
- 用来在各独立模块之间进行通信的事件库
- 用来管理URL和浏览器回退按钮的路由器(Router)
等等。
接着让我们引入riot.js这个库,顺便也引入rxjs吧:
<script src="{% static 'js/mobile/riot+compiler.min.js' %}"></script><script src="{% static 'js/mobile/rx.core.min.js' %}"></script>
ReactiveJS构建服务
由于我们所要做的服务比较简单,并且我们也更愿意使用Promise来加载API服务,因此我们引入了这个库来加速我们的开发。下面是我们用于获取博客API的代码:
var responseStream = function (blogId) {var url = '/api/blogpost/?format=json';if(blogId) {url = '/api/blogpost/' + blogId + '?format=json';}return Rx.Observable.create(function (observer) {jQuery.getJSON(url).done(function (response) {observer.onNext(response);}).fail(function (jqXHR, status, error) {observer.onError(error);}).always(function () {observer.onCompleted();});});};
当我们想访问特定博客的时候,我们就传博客ID进去——这时会使用'/api/blogpost/' + blogId + '?format=json'作为URL。接着我们创建了自己定制的事件流——使用jQuery去获取API:
- 成功的时候(done),我们将用onNext()来通知观察者
- 失败的时候(fail),我们就调用onError()来通知观察者
- 不论成功或者失败,都会执行always
在使用的时候,我们只需要调用其subscribe方法即可:
responseStream().subscribe(function (response) {})
创建博客列表页
现在,我们可以修改原生的博客模板,将其中的container内容变为:
<div class="container" id="container"><blog></blog></div>
接着,我们可以创建一个blog.tag文件,添加加载这个文件:`
<script src="{% static 'riot/blog.tag' %}" type="riot/tag"></script>
为了调用这个tag的内容,我们需要在我们的main.js加上一句:
riot.mount("blog");
随后我们可以在我们的tag文件中,来对blog的内容进行操作。
<blog class="row"><div class="col-sm-4" each={ opts }><h2><a href="#/blogDetail/{id}" onclick={ parent.click }>{ title }</a></h2>{ body }{ posted } - By { author }</div><script>var self = this;this.on('mount', function (id) {responseStream().subscribe(function (response) {self.opts = response;self.update();})})click(event){this.unmount();riot.route("blogDetail/" + event.item.id);}</script></blog>
在Riot中,变量默认是以opts的方式传递起来的,因此我们也遵循这个方式。在模板方面,我们遍历每个博客取出其中的内容:
<div class="col-sm-4" each={ opts }><h2><a href="#/blogDetail/{id}" onclick={ parent.click }>{ title }</a></h2>{ body }{ posted } - By { author }</div>
而博客的数据需要依赖于我们监听mount事件才会去获取——即我们加载了这个tag。
this.on('mount', function (id) {responseStream().subscribe(function (response) {self.opts = response;self.update();})})
在这个页面中,还有一个单击事件onclick={ parent.click },即当我们点击某个博客的标题时执行的函数:
click(event){this.unmount();riot.route("blog/" + event.item.id);}
我们将卸载当前的tag,然后加载blogDetail的内容。
博客详情页
在我们加载之前,我们需要先配置好blogDetail。我们仍然使用正则表达式blogDetail/*来获取博客的id:
riot.route.base('#');riot.route('blog/*', function(id) {riot.mount("blogDetail", {id: id})});riot.route.start();
然后将由相应的tag来执行:
<blogDetail class="row"><div class="col-sm-4"><h2>{ opts.title }</h2>{ opts.body }{ opts.posted } - By { opts.author }</div><script>var self = this;this.on('mount', function (id) {responseStream(this.opts.id).subscribe(function (response) {self.opts = response;self.update();})})</script></blogDetail>
同样的,我们也将去获取这篇博客的内容,然后显示。
添加导航
在上面的例子里,我们少了一部分很重要的内容就是在页面间跳转,现在就让我们来创建navbar.tag吧。
首先,我们需要重新规则一下route,在系统初始化的时候我们将使用的路由是blog,在进入详情页的时候,我们用blog/*。
riot.route.base('#');riot.route('blog/*', function (id) {riot.mount("blogDetail", {id: id})});riot.route('blog', function () {riot.mount("blog")});riot.route.start();riot.route("blog");
然后将我们的navbar标签放在blog和blogDetail中,如下所示:
<blogDetail class="row"><navbar title="{ opts.title }"></navbar><div class="col-sm-4"><h2>{ opts.title }</h2>{ opts.body }{ opts.posted } - By { opts.author }</div></blogDetail>当我们到了博客详情页,我们将把标题作为参数传给title。接着,我们在navbar中我们就可以创造一个breadcrumb导航了:
最后可以在我们的blogDetail标签中添加一个点击事件来跳转到首页:
clickTitle(event) {
self.unmount(true);
riot.route(“blog”);
}
```
