• 移动单页面应用
    • 移动设备处理
    • 前后端分离
      • Riot.js
      • ReactiveJS构建服务
      • 创建博客列表页
      • 博客详情页
      • 添加导航

    移动单页面应用

    为了实现在移动设备上的访问,这里就以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”);
    }
    ```