在一个普通的网站上,用户通过点击链接或者是填写表单来在不同页面间导航. 然而,在一个单页的应用程序中, 用户的交互不会加载一个新的页面. 取而代之的是在一个单页面用组件来对用户的交互进行响应. 那么你如何让用户使用浏览器的前进和后退按钮呢? 答案是用Ext JS 5的新的路由功能来响应 URI 的 Hash 变化.
路由用来做什么
路由能够通过使用浏览器的历史记录堆栈, 来跟踪应用程序的状态. 路由也允许深入链接到应用程序中的特定部分.
路由不能用来做什么
路由应该不要用来存储任何数据和会话信息, 数据应该存储在一个持久的数据源中, 例如 cookies 或者是本地存储. 路由仅仅是跟踪应用程序状态的一种手段.
Hash是什么?
浏览器通过使用由很多部分构成的 URI 来浏览互联网. 让我们来看一个简单的URI:
http://www.example.com/apps/users#user/1234
这个应该看起来比较熟悉. 然而你可能不认识 #user=1234
. URI 的这个部分叫做 “hash” 或者是片断标识符. 关于 hash 更为详细的信息, 请阅读资源 http://en.wikipedia.org/wiki/Fragment_identifier. 这个 hash 为应用程序提供了一个不重新加载当前页来控制浏览器的历史堆栈的方法. 由于 Hash 的变化, 浏览器将整个 URI 添加到历史堆栈,然后允许您使用浏览器的前进/后退按钮遍历 URI. 例如,将 hash 值更新为下面这样会发生什么:
http://www.example.com/apps/users#user/5678
浏览器会触 发一个我们能在应用程序里使用的 hashchange
事件.
用户然后能点击返回按钮返回到这个 #user=1234
的 hash 值. 此通知将允许您对应用程序中的更改作出反应. 有一点要注意的是 Hash 并没有发送给服务器. 这个 Hash 在客户端解析 URI 中被消化了. Ext Router (路由)依靠浏览器的 Hash 功能来允许应用程序的状态跟踪和深度链接.
在应用程序中实现路由
路由类是 Ext JS 5 新加功能, 目的是为了在 MVC 应用程序中可以很简单的处理 Hash 的变化. 我们有一个 Ext.util.History, 用于对 Hash 的变化作出反应. 然而这个 Ext.util.History 提供了更多手动的过程, 并且需要一个合适的路由类. Router (路由) 提供了一种简单的方式就可以集成到Ext JS 5 的MVC应用程序中, 只要在视图控制器中定义路由规则即可. 路由是匹配 Hash 的一个字符串, 并且允许在你的 Ext 应用程序中进行深度链接. 这里是一个实现了路由功能的简单示例控制器:
Ext.define('MyApp.view.main.MainController', {
extend : 'Ext.app.ViewController',
routes : {
'users' : 'onUsers'
},
onUsers : function() {
//...
}
});
这个路由会对 #users
Hash 作出反应, 然后在控制器实例作用域下执行 onUsers
方法. 正如你所看到的, routes 配置是放置在 config
对象中的,而不是在根部.
更新 Hash
为更新 Hash ,控制器有一个 redirectTo 方法.
this.redirectTo('user/1234');
这将更新 Hash 为 "#user/1234"
,然后路由配置了识别这个 Hash 和要运行的内容. 有时要更新的 Hash 可能与当前值相同. 在这种情况下, redirectTo 方法会返回
false
, 然后不会更新 Hash, 从而不会运行路由配置中的方法.redirectTo
也可以接受第二个参数传递 true 值来强制路由对 Hash 做出反应.
this.redirectTo('user/1234', true);
尽管当前的 Hash 匹配传到 redirectTo的值
, 这个路由仍然会执行任何匹配的路线.
默认令牌
当应用程序启动时如果没有提供 Hash 可以在配置中增加一个默认值.例如,当 #home Hash 使用时应用程序显示一个仪表盘,如果没有其它 Hash 存在,你就需要增回 #home Hash 给 URI. 为启用一个默认 Hash ,你能在应用程序里找到的 /app/view/Application.js
文件中使用 defaultToken
配置项:
Ext.define('MyApp.Application', {
extend : 'Ext.app.Application',你参在
//...
defaultToken : 'home'
});
当应用程序启动时, 会检查URI的当前 Hash. 如果有一个 Hash 定义了, 就会运行对应的路由处理方法. 如果没有 Hash, 就会将 #home
Hash 加入, 然后运行相应路由处理函数来进行适当的处理.
带参数的 Hash
应用程序也能在 Hash 里使用参数. 比如你可能要在 Hash 中包含一个用户的 ID 做为一个参数. 我们在这篇指南中早前提到过使用 #user/1234 做为一个 Hash
. 在这个例子中, 我们要让1234
做为一个 id 参数. 你要设置你的控制器对 #user/1234
Hash 作出响应:
Ext.define('MyApp.view.main.MainController', {
extend : 'Ext.app.ViewController',
routes : {
'user/:id' : 'onUser'
},
onUser : function(id) {
//...
}
});
我们使用的这个路由是 'user/:id'
和冒号, :
. 这意味着有一个参数而且应用程序会将这个参数作为 onUser 方法一个参数传入. 该方法会按照路由中指定的的顺序收到相同数量的参数, 这样就允许简单的包含多个参数.
Hash 参数格式化
该应用程序可能要给用户 ID 强制一个特定的格式. 在这个例子中, 我们目前看到的是个数字. 你还可以通过配置 conditions 来校验路由:
Ext.define('MyApp.view.main.MainController', {
extend : 'Ext.app.ViewController',
routes : {
'user/:id' : {
action : 'onUser',
conditions : {
':id' : '([0-9]+)'
}
}
},
onUser : function(id) {
//...
}
});
我们来看一下这个例子.首先, ‘onUser’ 方法现在移到了action 的配置项上. 这与我们传递字符串给路由类似.我们然后使用 conditions 配置项来提供一个对象. 我们要控制的关键字是参数名称和一个冒号然后跟着一个正则表达式字符串 (不是一个正则表达式对象). 对于 :id
条件, 我们用 ([0-9]+)
, 这个就只允许0-9之间的数字, 但是可以任意长度, 然后记住这个匹配关系. 因为正则表达式对象是用于匹配整个 Hash 的, 所以这里我们用字符串, 否则如果在路由中配置有多个参数, 我们就要将正则表达式字符串串成一个正则表达式对象. 如果没有为参数提供条件, 默认的是:
([%a-zA-Z0-9\\-\\_\\s,]+)
路由处理
有时应用程序需要阻止路由的运行. 在这种情况下, 我们要检查管理权限来确定是否允许当前用户查看应用程序的某一部分. 路由可以配置一个 before action, 这个会停止当前的路由, 停止所有路由或者继续路由的运行.
这个例子会继续运行路由函数:
Ext.define('MyApp.view.main.MainController', {
extend : 'Ext.app.ViewController',
routes : {
'user/:id' : {
before : 'onBeforeUser',
action : 'onUser'
}
},
onBeforeUser : function(id, action) {
Ext.Ajax.request({
url : '/security/user/' + id,
success : function() {
action.resume();
}
});
},
onUser : function(id) {
//...
}
});
在 onBeforeUser
方法中, 这个 :id
参数作为入参传入了,但最后一个参数是 action
. 如果你在action
上执行 resume
方法, 路由就会继续执行, 然后就会调用 onUser
方法. 注意我们在继续运行路由功能前, 我们要等待 AJAX 请求完成.
我们可以扩展这个示例, 通过运行 stop
方法来告诉应用停止运行当前的路由:
Ext.define('MyApp.view.main.MainController', {
extend : 'Ext.app.ViewController',
routes : {
'user/:id' : {
before : 'onBeforeUser',
action : 'onUser'
}
},
onBeforeUser : function(id, action) {
Ext.Ajax.request({
url : '/security/user/' + id,
success : function() {
action.resume();
},
failure : function() {
action.stop();
}
});
},
onUser : function(id) {
//...
}
});
Ext JS 应用程序可能相当复杂, 并且可能有许多控制器设置成监听同一路由. 然而只有一个控制器设置了 before 动作,因此只有一次 ajax 请求会被触发. 如果我们传递一个为 true
的参数给 action
的 stop
方法, 我们能够停止运行所有队列中的路由, 而不仅仅是当前的路由处理:
Ext.define('MyApp.view.main.MainController', {
extend : 'Ext.app.ViewController',
routes : {
'user/:id' : {
before : 'onBeforeUser',
action : 'onUser'
}
},
onBeforeUser : function(id, action) {
Ext.Ajax.request({
url : '/security/user/' + id,
success : function() {
action.resume();
},
failure : function() {
action.stop(true);
}
});
},
onUser : function(id) {
//...
}
});
现在, 当 ajax 请求失败时,我们传参 true
来取消所有控制器上的路由处理程序.
注意: 如果你不运行resume
或者 stop
方法, 路由会被中断并且永远不会正确地完成. 重要的是在某个时间点上运行这二个方法中的任意一个.
处理不匹配的路由
如果这个 hash 发生了变化而没有找到相匹配的路由, 路由处理器将不会做任何事; 路由处理程序将不会尝试修改这个hash将会单独保留这个不匹配的hash值. 路由处理程序将会在应用程序实例中触发一个 unmatchedroute
事件, 这个事件在 Ext.application
中是可监听的:
Ext.application({
name : 'MyApp',
listen : {
controller : {
'#' : {
unmatchedroute : 'onUnmatchedRoute'
}
}
},
onUnmatchedRoute : function(hash) {
//...
}
});
在一个单一 Hash 中使用多个路由
因为 Ext JS 应用程序能够做的非常复杂, 这就可能有时需要在一个单一 hash 中要使用多个路由. 路由(Router)支持这个功能, 因此不需要控制处理器中作额外的处理. 相反, 你能使用管道符号: |
来分隔多个 hash . 一个hash例子可能是这样
`#user/1234|messages`
在这个情况下,我们要要对一个 id 为 1234 的用户显示详细信息, 但也要显示消息. 每个路由会按hash中指定的顺序运行. 它们互不影响. 简单来说, 如果你中止这个 user/1234
路由, messages
路由会继续运行. 重要的是要注意单个路由的运行顺序和路由在 hash 中出现的顺序是一样的. 在上面的例子中,这个user/1234
路由总是会在 messages
路由前执行.
通过改变 Ext.app.route.Router.multipleToken
属性, 你可以修改这个分隔符.