Ember Route

简介

Ember Route,路由管理,在ember中具有很重要的意义,他负责管理整个路由的规则,什么时候应该渲染什么模板等,总体来说,他的功能有

  • 渲染一个模板
  • 加载model以供模板使用
  • 重定向到一个新的路由,比如说权限控制情况下,一个人不允许访问某一个页面
  • 可以负责处理一个action(动作或者事件)

基本配置

创建route

使用命令行创建ember g route <your-route-name>
执行命令后,会在app/templates目录下生成模板,会在app/routes目录下生成js文件,同时修改app/route.js文件。

这里主要说明一下route.js文件,其基本格式为:

Router.map(function() {
  this.route('path1');
  this.route('favorites', { path: '/path2' });
});

当访问/path1的时候,渲染path1模板,当访问/path2的时候,渲染favorites模板。
如果是嵌套的路由(子路由),比如path1/path2,那么形式可能是这样的, 创建的时候只需要ember g route path1/path2

Router.map(function() {
  this.route('path1',function(){
      this.route('path2');
  });
});

如果想要设置初始页面,可以这样,这样就可以为/设置一个初始的路由:

Router.map(function() {
  this.route('index',{path:'/'});
});

如果url中具有动态的参数呢?例如localhost:8000/path1/path2/2,这个2可能是一个id,又或者localhost:8000/path1/path2/2/edit这可能代表一个id为2的数据的编辑页面,那么在route.js可以如下表示:

Router.map(function() {
  this.route('path1', function(){
      this.route('path2',{path:'/path2/:id'});
  });
});

通过这种方式,就可以进行访问,但是注意,以上的形式下localhost:8000/path1/path2是无法访问的。
在这种情况下,如何使用这个id参数呢?

//app/routes/path1/path2
export default Ember.Route.extend({
    model(params){
        let id = params.id;
        return this.store.findRecord('model-example',id).then((data)=>{return data});
    }
});

如上就可以对参数进行使用。理论上来讲,是不能够修改url中的参数的,所以手动赋值是无效的,也无法进行刷新等动作。同时需要注意,不要给参数起一样的名字,那样就会不起作用,下面的李自力有2个id,所以不会起作用:

Router.map(function() {
  this.route('photo', { path: '/photo/:id' }, function() {
    this.route('comment', { path: '/comment/:id' });
  });
});

路由、模板的渲染规则

所有的路由都有一个根的父路由:application,也就是说,我们创建的所有路由,都是经由application渲染的。
我们我们查看模板文件,会发现路由的模板的内容都是{{outlet}}。每一个模板都会渲染到父模板的{{outlet}}上。一层一层的往上渲染。
比如经典的圣杯模型,在我们看来,可能上边栏侧边栏和下边栏是不变的,那么我们可以定义我们的application.hbs为:

<div class="header">这是上边栏</div>
<div class="sider">这是侧边栏</div>
<div class="content">
{{outlet}}
</div>
<div class="footer">这是下边栏</div>

那么渲染的时候,只会渲染到content中。
接下来我们讲解一下渲染的规则。这里这篇文章介绍的非常好Ember.js 入门指南之十四番外篇,路由、模板的执行、渲染顺序

渲染其他的模板也是允许的

一般情况下,我们要渲染的模板都是自己的哪一个,但是,ember也允许你渲染其他的模板,例如正常情况下,不做任何操作,path1/path2渲染的模板就是app/templates/path1/path2.hbs, 但是如果你想要进行更换,只需要重载方法renderTemplate()

//app/routes/path1/path2.js
export default Ember.Route.extend({
    renderTemplate() {
        this.render('path1/path2', {
          into: 'templateName',
          outlet: 'anOutletName'
        });
        //this.render('path3');//这个语句可以使用path3替换path1/path2
    }
});

上面这个方法可以将path1/path2渲染到templateName 中使用{{outlet 'anOutletName'}}的地方。详情请见render

model的执行顺序

model的执行顺序为从主到子。例如urlpath1/path2,那么model的执行顺序为application->path1->path2

模板的渲染顺序

首先,注意一点,就是,如果model执行完成后,才开始进行模板的渲染。
模板渲染的顺序为从子到主,也就是path2->path1->application->path1。渲染完成后,展示。这里需要全部渲染完后展示。

路由的重定向

路由重定向,例如,当权限控制等的时候,跳转到一个异常页面等,常常会用到,常用方法为transitionTo()transitionTo()的表现和helperlink-to的表现一致。另外还有一个方法replaceWith(),使用起来差不多。

transitionTo()可以在各个阶段使用:

//app/routes/path1.js
export default Ember.Route.extend({
    //在model初始化之前(发送请求之前)
    beforeModel(){
        this.transitionTo('/path2');
    },
    //在初始化的时候,
    model(){
        return this.findRecord('model-example',1).then((data)=>{
            if(!data){
                this.transitionTo('/path2');
            }else{
                return data;
            }
        });
    }
    //在model初始化之后
    afterModel(){
        let model = this.modelFor('path1');
        if(model.length){
            this.transitionTo('/path2');
        }
    },
    actions:{
        //可以在action中,比如点击后
        transitionToPath2(){
            this.transitionTo('/path2');
        }
    }
});

如果是要重定向到子路由,也可以直接在beforeModel() afterModel() model()中进行,但是如果这样的,考虑到子路由的执行过程,会再次将父路由执行一次,所有,ember有一个更加优化的方式redirect

//app/routes/path1.js
redirect(model, transition) {  
    this.transitionTo('path1.path2');
}

还有一些高级的用法,比如复杂url或者带参数的情况,注意这种情况下不能使用replaceWith进行替换:

//路由为 this.route('path2',{path:path2/:id});
this.transitionTo('/path2/2');
this.transitionTo('/path2',2);//同上
{{link-to 'path2' 2}}

//路由为 this.route('path2',{path:path2/:id}, function(){this.route('path3',{path:'path3/:p_id'})});
this.transitionTo('/path2/2/paht3/3');
{{link-to 'path2' 2 3}}

//路由为this.route('path2',{path:path2/:id}); 且有参数`queryParams`(这个概念会在controller中说)
this.transitionTo('/path2/2?params=1');
this.transitionTo('/path2/2',{queryParams: { params: '1' }});//同上
{{link-to 'path2' 2 (query-params params='1')}}
终止与重试路由跳转

有一些情况下,我们可能需要手动终止路由的跳转,或者是终止之后再重试。

当用户通过{{link-to}}、transition方法或者直接执行URL来转换路由,当前路由会自动执行willTransition方法。每个活动的路由都可以决定是否执行转换路由。
如果你正在填写一个表单,然后,无意点击了跳转,这样你的填写的数据就都丢失了,很不友好。所以一般会有一个确认页面。所以我们可以通过willTransition来先让用户确认是否离开再说。

//  app/routes/path1.js
import Ember from 'ember';
export default Ember.Route.extend({  
    actions: {
        willTransition: function(transition) {
            if (!confirm("你确定要离开这个页面吗?")) {
                transition.abort();
            } else {
                return true;
            }
        }
    }
});

load 以及 error的处理

我们假设有这样的一个model:slow-model,这个model返回结果非常的慢,那么我们的以routedemo1/case1为例,如果route中加载情况如下:

model(){
    return this.store.findAll('slow-model').then((data)=>{
        return data;
    });
}

那么,在slow-model返回结果之前,页面会一直是空白,这个非常不美观,我们一般会采用一个loading的图标或者有一个提示等。

Ember提供的解决办法是:在beforeModel、model、afterModel回调还没返回前先进入一个叫loading的子状态,然后渲染一个叫<your-route-name>-loading的模板(如果是application路由则对应的直接是loading、error不需要前缀)。

同样以demo1/path1为例:

//app/templates/application.hbs
<p>application</p>
<p>application</p>
<p>application</p>
{{outlet}}
//app/routes/demo1.js
import Ember from 'ember';
export default Ember.Route.extend({
    model() {
        return this.store.findAll('slow-model');
    }
})
//app/templates/demo1.hbs
<p>demo1</p>
<p>demo1</p>
<p>demo1</p>
{{outlet}}
//app/templates/demo1-loading.hbs
<p>demo1-loading</p>
<p>demo1-loading</p>
<p>demo1-loading</p>
//app/routes/demo1/case1.js
import Ember from 'ember';
export default Ember.Route.extend({
    model() {
        return this.store.findAll('slow-model');
    }
//app/templates/demo1/case1.hbs
<p>case1</p>
<p>case1</p>
<p>case1</p>
//app/templates/demo1/case1-loading.hbs
<p>case1-loading</p>
<p>case1-loading</p>
<p>case1-loading</p>

以上的代码,假设slow-model是非常慢的,那么页面的展示顺序为:

这里写图片描述
这里写图片描述
这里写图片描述

demo1/case1为例,结合我们说过的路由渲染顺序为,注意,渲染顺序还是从子到父的:
- demo1.case1-loading
- demo1-loading
- loading(application的loading)

从上图可以看出,在model没有加载好的时候,会先用<your-route-name>-loading来对本来的模板进行替换。那么在这个过程中,model的执行顺序呢?这个过程中,model为先执行父route的model,也就是demo1的model,当请求发出,直到下一个子路由的mode请求发出,一直都是loading状态,也就是:
这里写图片描述
然后子路由请求发出,也就是demo1/case1请求发出后,进入子路由的loading状态:
这里写图片描述
数据返回后,结束:
这里写图片描述

另外,在beforeModel、model、afterModel回调没有立即返回之前,会先执行一个名为loading的事件。

    actions: {
        loading: function(transition, originRoute) {
            alert("Sorry this is taking so long to load!!");
        }
    }

loading事件实在model回调返回之前执行。这里需要注意一下,如果同时定义了loading回调,以及<your-route-name>-loading子模板,那么会只执行loading回调,而不会渲染模板。

error的情况也类似,不过过程是在model返回且失败的情况下,而loading为未返回的情况下:
demo1/case1为例,结合我们说过的路由渲染顺序为,注意,渲染顺序还是从子到父的(考虑到model的执行顺序,不会出现父吧子覆盖的情况,因为执行不到- -):
- demo1.case1-error
- demo1-error
- error(application的error)

error的回调为:

actions: {
    error(error, transition) {
      if (error.status === '403') {
        this.replaceWith('login');
      } else {
        // Let the route above this handle the error.
        return true;
      }
    }
  }

查询参数

动态路由的情况,我们已经说过,但是,如果是带参数呢?举例,我们常用的分页操作,一般都会带上pageSize=10&pageNum=2意味着第二页取10个,这样,那么在请求中怎么做呢?
这里就要使用到controller的概念,对于路由demo1/case1而言,如果要增加参数,那么就需要创建相应的controller。创建controller:ember g controller demo1/case1,以分页为例:

//app/controllers/demo1/case1.js
import Ember from 'ember';
export default Ember.Controller.extend({
  queryParams: ['pageSize','pageNum'],
  pageSize: null,
  pageNum:null
});

这样就会在请求上带上这个参数,如果你没带的话,还是会取到这个参数,只不过都是null罢了。你可以为他们设置默认值:

//app/controllers/demo1/case1.js
import Ember from 'ember';
export default Ember.Controller.extend({
  queryParams: ['pageSize','pageNum'],
  pageSize: 10,
  pageNum:1
});

那么默认就是第一页的10个。注意,如果你传的值就是和默认的一样,在请求上是不会展示的!

关于查询参数的用法,跳转,在之前的transitionTo()方法以及link-to助手的的时候已经说过。

controller中还可以调用model的数据,以及使用计算属性,官网上给出一个例子:

import Ember from 'ember';

export default Ember.Controller.extend({
  queryParams: ['category'],
  category: null,

  filteredArticles: Ember.computed('category', 'model', function() {
    var category = this.get('category');
    var articles = this.get('model');

    if (category) {
      return articles.filterBy('category', category);
    } else {
      return articles;
    }
  })
});

这样,filteredArticles的值就会一直随着model的改变而改变,而filteredArticles的值是可以直接在模板中使用的,所以说间接的导致模板元素的变化。

关于查询参数,还有一个点很重要,就是更新数据。因为毕竟url改变了,如果数据却没有更新的话,简直不合理。

上述的做法相当于这样:

this.transitionTo({ queryParams: { pageNum: 3 }});
{{link-to (query-param pageNum=3)}}

注意,这种方式,由于没有修改路由的结构,只不过是修改了参数,所以只能说是不完整的路由切换,这种不完整,也就意味着比如model和setupController回调方法就不会被执行,只是使得controller里的属性值为新的查询参数值以及更新URL。(当然,如果是跳转到一个新的路由,还是会改变的)。

如果单纯的按照上面的写法,model是不会更新的。那么如何更新model中的数据呢?这需要你在对应的路由中配置一个名为queryParams哈希对象。并且需要设置一个名为refreshModel的查询参数,这个参数的值为true。

import Ember from 'ember';

export default Ember.Route.extend({
  queryParams: {
    category: {
      refreshModel: true
    }
  },
  model(params) {
    // This gets called upon entering 'articles' route
    // for the first time, and we opt into refiring it upon
    // query param changes by setting `refreshModel:true` above.

    // params has format of { category: "someValueOrJustNull" },
    // which we can forward to the server.
    return this.get('store').query('article', params);
  }
});

那么每次你修改参数后,都会更新model并且执行setupController。但是要注意,这个也会同时刷新父路由!

那么如果要手动触发呢?比如搜索条件,你想要点击搜索的时候才更新数据,或者说只更新自己的路由的信息,怎么办呢?那么可以这么做

import Ember from 'ember';

export default Ember.Route.extend({
    model(){
        console.log('model');
    },
    setupCotroller(controller,model){
        console.log('setupcontroller');
    },
    actions:{
        //如果只需要更新model
        clickAction(){
            this.refresh();
        },
        //同时更新model以及setupcontroller
        clickAction1(){
            let self = this, controller=this.get('controller');
            this.model().then((data)=>{
                controller.set('model', data);
                self.setupController(controller,data); 
            })
        }
    }
});

注意上面的方法都只能更新自己的model,如果想要更新父model呢?参考如下:

import Ember from 'ember';

export default Ember.Route.extend({
    model(){
        console.log('model');
    },
    setupCotroller(controller,model){
        console.log('setupcontroller');
    },
    actions:{
        //更新父model
        clickAction(){
            //this.refresh();//这个只能更新自己的model
            this.modelFor('parent').reload();//指定父model然后reload可以实现更新
        }
    }
});

异步路由

//TODO

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值