移动单页面应用

为了实现在移动设备上的访问,这里就以riot.js为例做一个简单的Demo。不过,首先我们需要在后台判断用户是来自于某种设备,再对其进行特殊的处理。

移动设备处理

幸运的是我们又找到了一个库名为django_mobile,可以根据用户的User-Agent来区别设备,并为其分配一个移动设备专用的模板。因此,我们需要安装这个库:

  1. pip install django_mobile

并将'django_mobile.middleware.MobileDetectionMiddleware''django_mobile.middleware.SetFlavourMiddleware'添加MIDDLEWARE_CLASSES中:

  1. MIDDLEWARE_CLASSES = (
  2. 'django.contrib.sessions.middleware.SessionMiddleware',
  3. 'corsheaders.middleware.CorsMiddleware',
  4. 'django.middleware.common.CommonMiddleware',
  5. 'django.middleware.csrf.CsrfViewMiddleware',
  6. 'django.contrib.auth.middleware.AuthenticationMiddleware',
  7. 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
  8. 'django.contrib.messages.middleware.MessageMiddleware',
  9. 'django.middleware.clickjacking.XFrameOptionsMiddleware',
  10. 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
  11. 'django_mobile.middleware.MobileDetectionMiddleware',
  12. 'django_mobile.middleware.SetFlavourMiddleware'
  13. )

修改Template配置,添加对应的loader和context_processor,如下所示的内容即是修改完后的结果:

  1. TEMPLATES = [
  2. {
  3. 'BACKEND': 'django.template.backends.django.DjangoTemplates',
  4. 'DIRS': [
  5. 'templates/'
  6. ],
  7. 'OPTIONS': {
  8. 'context_processors': [
  9. 'django.template.context_processors.debug',
  10. 'django.template.context_processors.request',
  11. 'django.contrib.auth.context_processors.auth',
  12. 'django.contrib.messages.context_processors.messages',
  13. 'django_mobile.context_processors.flavour'
  14. ],
  15. },
  16. },
  17. ]
  18. # dirty fixed for https://github.com/gregmuellegger/django-mobile/issues/72
  19. TEMPLATE_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吧:

  1. <script src="{% static 'js/mobile/riot+compiler.min.js' %}"></script>
  2. <script src="{% static 'js/mobile/rx.core.min.js' %}"></script>

ReactiveJS构建服务

由于我们所要做的服务比较简单,并且我们也更愿意使用Promise来加载API服务,因此我们引入了这个库来加速我们的开发。下面是我们用于获取博客API的代码:

  1. var responseStream = function (blogId) {
  2. var url = '/api/blogpost/?format=json';
  3. if(blogId) {
  4. url = '/api/blogpost/' + blogId + '?format=json';
  5. }
  6. return Rx.Observable.create(function (observer) {
  7. jQuery.getJSON(url)
  8. .done(function (response) {
  9. observer.onNext(response);
  10. })
  11. .fail(function (jqXHR, status, error) {
  12. observer.onError(error);
  13. })
  14. .always(function () {
  15. observer.onCompleted();
  16. });
  17. });
  18. };

当我们想访问特定博客的时候,我们就传博客ID进去——这时会使用'/api/blogpost/' + blogId + '?format=json'作为URL。接着我们创建了自己定制的事件流——使用jQuery去获取API:

  • 成功的时候(done),我们将用onNext()来通知观察者
  • 失败的时候(fail),我们就调用onError()来通知观察者
  • 不论成功或者失败,都会执行always

在使用的时候,我们只需要调用其subscribe方法即可:

  1. responseStream().subscribe(function (response) {
  2. })

创建博客列表页

现在,我们可以修改原生的博客模板,将其中的container内容变为:

  1. <div class="container" id="container">
  2. <blog></blog>
  3. </div>

接着,我们可以创建一个blog.tag文件,添加加载这个文件:`

  1. <script src="{% static 'riot/blog.tag' %}" type="riot/tag"></script>

为了调用这个tag的内容,我们需要在我们的main.js加上一句:

  1. riot.mount("blog");

随后我们可以在我们的tag文件中,来对blog的内容进行操作。

  1. <blog class="row">
  2. <div class="col-sm-4" each={ opts }>
  3. <h2><a href="#/blogDetail/{id}" onclick={ parent.click }>{ title }</a></h2>
  4. { body }
  5. { posted } - By { author }
  6. </div>
  7. <script>
  8. var self = this;
  9. this.on('mount', function (id) {
  10. responseStream().subscribe(function (response) {
  11. self.opts = response;
  12. self.update();
  13. })
  14. })
  15. click(event)
  16. {
  17. this.unmount();
  18. riot.route("blogDetail/" + event.item.id);
  19. }
  20. </script>
  21. </blog>

在Riot中,变量默认是以opts的方式传递起来的,因此我们也遵循这个方式。在模板方面,我们遍历每个博客取出其中的内容:

  1. <div class="col-sm-4" each={ opts }>
  2. <h2><a href="#/blogDetail/{id}" onclick={ parent.click }>{ title }</a></h2>
  3. { body }
  4. { posted } - By { author }
  5. </div>

而博客的数据需要依赖于我们监听mount事件才会去获取——即我们加载了这个tag。

  1. this.on('mount', function (id) {
  2. responseStream().subscribe(function (response) {
  3. self.opts = response;
  4. self.update();
  5. })
  6. })

在这个页面中,还有一个单击事件onclick={ parent.click },即当我们点击某个博客的标题时执行的函数:

  1. click(event)
  2. {
  3. this.unmount();
  4. riot.route("blog/" + event.item.id);
  5. }

我们将卸载当前的tag,然后加载blogDetail的内容。

博客详情页

在我们加载之前,我们需要先配置好blogDetail。我们仍然使用正则表达式blogDetail/*来获取博客的id:

  1. riot.route.base('#');
  2. riot.route('blog/*', function(id) {
  3. riot.mount("blogDetail", {id: id})
  4. });
  5. riot.route.start();

然后将由相应的tag来执行:

  1. <blogDetail class="row">
  2. <div class="col-sm-4">
  3. <h2>{ opts.title }</h2>
  4. { opts.body }
  5. { opts.posted } - By { opts.author }
  6. </div>
  7. <script>
  8. var self = this;
  9. this.on('mount', function (id) {
  10. responseStream(this.opts.id).subscribe(function (response) {
  11. self.opts = response;
  12. self.update();
  13. })
  14. })
  15. </script>
  16. </blogDetail>

同样的,我们也将去获取这篇博客的内容,然后显示。

添加导航

在上面的例子里,我们少了一部分很重要的内容就是在页面间跳转,现在就让我们来创建navbar.tag吧。

首先,我们需要重新规则一下route,在系统初始化的时候我们将使用的路由是blog,在进入详情页的时候,我们用blog/*。

  1. riot.route.base('#');
  2. riot.route('blog/*', function (id) {
  3. riot.mount("blogDetail", {id: id})
  4. });
  5. riot.route('blog', function () {
  6. riot.mount("blog")
  7. });
  8. riot.route.start();
  9. riot.route("blog");

然后将我们的navbar标签放在blogblogDetail中,如下所示:

  1. <blogDetail class="row">
  2. <navbar title="{ opts.title }"></navbar>
  3. <div class="col-sm-4">
  4. <h2>{ opts.title }</h2>
  5. { opts.body }
  6. { opts.posted } - By { opts.author }
  7. </div>
  8. </blogDetail>
  9. 当我们到了博客详情页,我们将把标题作为参数传给title。接着,我们在navbar中我们就可以创造一个breadcrumb导航了:



  1. 最后可以在我们的blogDetail标签中添加一个点击事件来跳转到首页:

clickTitle(event) {
self.unmount(true);
riot.route(“blog”);
}
```