Crossroads.js 是基于路由/分发 Route/Dispatch 的 JS 专业路由库
Crossroads.js 是一个受 Rails、Pyramid、Django、CakePHP 等基于路由/分发(Route/Dispatch)方式处理路由的后端 MVC 框架启发的一套js专业路由库。它能够直接解析传入的字符串并根据相应的规则来过滤和验证路由,然后再执行下一步的操作。
如果正确使用,它可以通过解耦对象和抽象导航路径来减少代码复杂度。
介绍
Crossroads.js 是一个独立的库功能十分强大和灵活,而且职责单一、目标定位清晰。如果在项目中应用适当不尽可以减少代码的复杂度,同时也可以通过在页面中使用 hash 路由方式来抽象页面路径和服务器请求。当然在开发 SPA 页面过程中,如果我们不使用 backbone、Angular 等类似的 MVC 库来开发 SPA 的话,它是一个相当不错的选择。当然还有一个 router.js 也是一个相当不错的独立路由插件。
Crossroads.js 由 Miller Medeiros 开发,它的使用不依赖于第三方库,内部仅仅只依赖于作者另一个 JS 库 signals.js:将 ActionScript 一个开源库 as3-signals 移植到了 JS。
由于 Crossroads.js 只定义了路由配置和规范,当页面监有路由请求信号发出时 Crossroads.js 会监听到信号,同时浏览器地址栏中的地址栏也会指向目标地址,但还需要一个处理页面 hashchange 事件的库。这里可以使用作者开发的库 hasher.js 或者使用 history.js 来处理 hashchange 事件。
使用方法
下面我将和大家一起来了解 Crossroads.js 的用法:
首先引入相应的js库:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>路由插件(crossroads.js)</title> <script src="js/jquery-1.11.2.min.js"></script> <script src="js/signals.min.js"></script> <script src="js/hasher.min.js"></script> <script src="js/crossroads.min.js"></script> </head> <body> <h1>路由插件(crossroads.js)</h1> <script> $(function() { // 这里输入内容 }); </script> </body> </html>
在 Crossroads.js 中定义一个路由可以使用 crossroads.addRoute(pattern, [handler], [priority]):Route 方法,可以接受三个参数:
- parttern: 对应路由规则,可以为字符串或者正则表达式
- handler: 可选参数,当监听到路由时的处理方法
- priority: 可选参数,定义路由在路由队列中的优先级
从函数的定义中可以看出其返回一个 Route 对象,后面会讲解 Route 对象相关属性和方法。在 Crossroads.js 中我们可以通过以下两种方式定义一个路由:
定义路由时同时定义操作
crossroads.addRoute('projects/{projectsType}', function(projectsType) { console.log(projectsType); });
只定义一个路由,返回一个'Route'对象
var projectsRoute = crossroads.addRoute('projects/{projectsType}'); // 这里通过'Route.matched'信号来处理操作 projectsRoute.matched.add(function(projectsType) { console.log(projectsType); }); // 通过'crossroads.parse()'方法来解析一个路由 crossroads.parse('/projects/sell'); // 输出: sell
如果需要带查询参数,在crossroads.js中可以通过对每个分组名前添加”?“(例如:{?foo}、:?bar:)来实现。
crossroads.addRoute('projects/{projectsType}/{?filter}', function(projectsType, filter) { console.log("项目类型:" + projectsType + ';\n' + "排序:" + filter.sort + ';\n' + "项目ID:" + filter.category + ';\n' + "分类:" + filter.attribute + ';'); }); crossroads.parse('projects/latest?attribute=online&category=927151&sort=2');
解析后所有匹配的值会被转换到一个对象中,默认情况下crossroads.js不会关心crossroads.parse()中传入的数据类型,传入的数字也会当成字符串处理。如果设置crossroads.shouldTypecast = ture, 那么会对处理后的对象值做出相应的类型判断。
crossroads.addRoute('projects/{projectsType}', function(projectsType) { console.log(projectsType); console.log(typeof projectsType); }); crossroads.parse('/projects/sell'); // 输出: sell和string crossroads.parse('/projects/12323'); // 输出: 12323和string // 将对象类型检查开启 crossroads.shouldTypecast = true; crossroads.parse('/projects/sell'); // 输出: sell和string crossroads.parse('/projects/12323'); // 输出: 12323和number
在定义路由表达式时也可以通过用 :: 包含一个字符或正则来实现可选路由(optional segments):
crossroads.addRoute('foo/:bar:/:far:', function(bar, far) { console.log(bar + " " + far); }); crossroads.parse('foo'); // undefined undefined crossroads.parse('foo/abc'); // abc undefined crossroads.parse('foo/abc/xyz'); // abc xyz
如果需要更多的可选路由(在其官网中称为:’rest’ segments),而且长度不确定,那么可以通过在分组尾部以’*’结尾来实现。
crossroads.addRoute('foo/:bar*:', function(bar) { console.log(bar); }); crossroads.parse('foo'); // undefined crossroads.parse('foo/abc'); // abc crossroads.parse('foo/abc/xyz'); // abc/xyz crossroads.parse('foo/abc/xyz/123'); // abc/xyz/123
在路由解析过程中,如果同一个地址能够被多个规则解析,那么可以通过指定crossroads.addRoute()第三个参数来提升优先级
/* 默认情况下按照声明先后顺序解析 */ crossroads.addRoute("bar/{lorem}", function(lorem) { console.log(lorem); }); crossroads.addRoute("bar/{lorem}/:date:", function(lorem, date) { console.log(lorem + ' : ' + date); }); crossroads.parse("bar/1"); // 1 crossroads.parse("bar/abc"); // abc crossroads.parse("bar/abc/2015-3-11"); // abc : 2015-3-11 /* 下面通过设置优先级来改变默认解析顺序 */ // 默认情况下优先级为'0' crossroads.addRoute("bar/{lorem}", function(lorem) { console.log(lorem); }, 0); // 指定第二个路由优先级为'1' crossroads.addRoute("bar/{lorem}/:date:", function(lorem, date) { console.log(lorem + ' : ' + date); }, 1); crossroads.parse("bar/1"); // 1 : undefined crossroads.parse("bar/abc"); // abc : undefined crossroads.parse("bar/abc/2015-3-11"); // abc : 2015-3-11
Crossroads.js 既然能够创建路由,当然也有移除路由对应的方法:crossroads.removeRoute(route) 以及 crossroads.removeAllRoutes():
// 路由一 var a = crossroads.addRoute("foo/{bar}", function(bar) { console.log("第一个路由:" + bar); }); // 路由二 crossroads.addRoute("bar/{lorem}", function(lorem) { console.log("第二个路由:" + lorem); }); // 路由三 crossroads.addRoute("lorem/{ipsum}", function(ipsum) { console.log("第三个路由:" + ipsum); }); crossroads.parse('foo/route1'); // 第一个路由:route1 crossroads.parse('bar/route2'); // 第二个路由:route2 crossroads.parse('lorem/route3'); // 第三个路由:route3 console.log(crossroads.getNumRoutes()); // 3 // 移除路由一 crossroads.removeRoute(a); // 第一个路由已从路由队列中移除了,不再接受解析 crossroads.parse('foo/route4'); crossroads.parse('bar/route5'); // 第二个路由:route5 crossroads.parse('lorem/route6'); // 第三个路由:route6 console.log(crossroads.getNumRoutes()); // 2 // 移除所有路由 crossroads.removeAllRoutes(); // 下面三个解析都没有任何作用,因为当前没有可解析的路由 crossroads.parse('foo/route7'); crossroads.parse('bar/route8'); crossroads.parse('lorem/route9'); console.log(crossroads.getNumRoutes()); // 0
注意在上面示例中,由于 crossroads.removeRoute() 需要一个 Route 对象作为参数,所以最好以第二种方式来定义一个路由规则,同时也方便路由统一配置和管理。crossroads.getNumRoutes() 方法用于获取当前路由队列中路由数量。
使用示例
Read the documentation for a detailed explanation of available methods and features.
Basic usage
crossroads.addRoute('/news/{id}', function(id){
console.log(id);
});
crossroads.parse('/news/123'); //will match '/news/{id}' route passing 123 as param
Optional parameters
Optional segments are very useful since they can drastically reduce the amount of routes required to describe the whole application.
//{id} is required, :date: is optional
crossroads.addRoute('/news/{id}/:date:', function(id, date){
console.log(id +' - '+ date);
});
crossroads.parse('/news/123'); //match route and pass "123" as param
crossroads.parse('/news/45/2011-09-31'); //match route passing "45" and "2011-09-31" as param
Rest segments (v0.8.0+)
Rest segments are useful in cases where you might want to match an arbitrary number of segments.
// {section*} will match multiple segments
crossroads.addRoute('/news/{section*}/{id}', function(section, id){
console.log(section +' - '+ id);
});
//match route and pass "lorem" and "123"
crossroads.parse('/news/lorem/123');
//match route passing "lorem/ipsum/dolor" and "45"
crossroads.parse('/news/lorem/ipsum/dolor/45');
Decode Query String (v0.9.0+)
If a capturing group starts with ?
it will be decoded into an object.
crossroads.addRoute('foo.php{?query}', function(query){
console.log(query.lorem +' - '+ query.dolor);
});
// match route passing {lorem:"ipsum", dolor:"amet"}
crossroads.parse('foo.php?lorem=ipsum&dolor=amet');
RegExp route
//capturing groups are passed as parameters to listeners
crossroads.addRoute(/^news\/([0-9]+)$/, function(id){
console.log(id);
});
crossroads.parse('/news/123'); //will match route passing "123" as param
crossroads.parse('/news/qwerty'); //won't match route
Store route reference and attach mutiple listeners
var articleRoute = crossroads.addRoute('/article/{category}/{name}');
articleRoute.matched.add(function(category, name){
console.log(category);
});
articleRoute.matched.add(function(category, name){
console.log(name);
});
//will match articleRoute passing "lol_catz" and "keyboard_cat" as param
crossroads.parse('/article/lol_catz/keyboard_cat');
Use RegExp to validate params
var specialNews = crossroads.addRoute('/news/{id}');
specialNews.matched.add(function(id){
console.log(id);
});
specialNews.rules = {
id : /^[0-9]+$/ //match only numeric ids
};
crossroads.parse('/news/asd'); //won't match since ID isn't numeric
crossroads.parse('/news/5'); //will match `specialNews` and pass "5" as param to all listeners
crossroads.parse('/news/5asd'); //won't match since ID isn't numeric
Use functions to validate params
This feature is very useful and gives a lot of flexibility on the validation.
var specialNews = crossroads.addRoute('/news/{category}/{id}');
specialNews.matched.add(function(id){
console.log(id);
});
specialNews.rules = {
//function should return a boolean value
id : function(val, request, values){
return val !== 'foo' && values.category !== 'bar';
}
};
crossroads.parse('/news/world/asd'); //will match
crossroads.parse('/news/bar/5'); //won't match
Use arrays to validate params
var specialNews = crossroads.addRoute('/news/{id}');
specialNews.matched.add(function(id){
console.log(id);
});
specialNews.rules = {
//segments are treated as strings unless `crossroads.shouldTypecast = true` (default = false)
id : ['asd', '5', '123', '23456', 'qwerty']
};
crossroads.parse('/news/asd'); //will match
crossroads.parse('/news/5'); //will match
crossroads.parse('/news/loremipsum'); //won't match
Validate whole request
var specialNews = crossroads.addRoute('/news/{id}');
specialNews.matched.add(function(id){
console.log(id);
});
specialNews.rules = {
//request_ will accept any kind of validation rule (function, array, RegExp)
request_ : ['/news/asd', '/news/5']
};
crossroads.parse('/news/asd'); //will match
crossroads.parse('/news/5'); //will match
crossroads.parse('/news/loremipsum'); //won't match
Normalize parameters & add default parameters
This feature is very powerful and can simplify a lot the logic of you application, it makes it easy to create multiple alias to the same route (see #21) and to set default parameters to the matched
signal.
function onSectionMatch(sectionId, subId){
console.log(sectionId +' - '+ subId);
}
var newsRoute = crossroads.addRoute('/news/{date}/{id}', onSectionMatch);
newsRoute.rules = {
normalize_ : function(request, vals){
//return parameters that should be passed to matched signal listeners
return ['editorial', vals.id];
}
};
crossroads.parse('/news/2011-08-32/123'); //will log "editorial - 123"
Default arguments (v0.8.0+)
If you are using crossroads to route nodejs requests you can use the crossroads.parse()
second argument to specify the default arguments passed to all handlers. Very useful in case you need to access the request and response objects.
var http = require('http'),
url = require('url'),
crossroads = require('crossroads');
http.createServer(function (req, res) {
//pass request and response as first arguments to all signals
crossroads.parse( url.parse(req.url).pathname, [req, res] );
}).listen(1337, "127.0.0.1");
Typecast parameters
crossroads.addRoute('/foo/{val}', function(val){
console.log(typeof val);
});
crossroads.shouldTypecast = false; //default = false
crossroads.parse('/foo/false'); //log "string"
crossroads.parse('/foo/true'); //log "string"
crossroads.parse('/foo/123'); //log "string"
crossroads.parse('/foo/abc'); //log "string"
crossroads.shouldTypecast = true; //default = false
crossroads.parse('/foo/false'); //log "boolean"
crossroads.parse('/foo/true'); //log "boolean"
crossroads.parse('/foo/123'); //log "number"
crossroads.parse('/foo/abc'); //log "string"
Check if route match a string
var myRoute = crossroads.addRoute('/foo/{id}');
console.log( myRoute.match('/foo/bar') ); //true
Create a string that matches the route (v0.9.0+)
var myRoute = crossroads.addRoute('foo/{id}/:slug:');
myRoute.interpolate({id: 123, slug: 'something'}); // "foo/123/something"
myRoute.interpolate({id: 456}); // "foo/456"
Dispose route
var myRoute = crossroads.addRoute('/foo/{id}');
myRoute.dispose(); //remove route from crossroads and also remove all listeners from route.matched
Create a new Router instance
You can create multiple Routers if needed. The new instance will work exactly like the crossroads
object and it is totally independent.
var sectionRouter = crossroads.create();
sectionRouter.addRoute('/foo');
console.log( sectionRouter.getNumRoutes() ); // 1
Piping multiple routers (v0.11.0+)
It's often a better practice to use multiple routers than greedy routes (more granular control and split responsibility), so after v0.11.0 you can also pipe/chain multiple routers.
var sectionRouter = crossroads.create();
var navRouter = crossroads.create();
sectionRouter.pipe(navRouter);
// will also call `parse()` on `navRouter`
sectionRouter.parse('foo');
// there is also an `unpipe()` method
// sectionRouter.unpipe(navRouter);
Validate RegExp route capturing groups
You can also validate each capturing group (the same way you do with the normal segments):
var awesomeRoute = crossroads.addRoute(/^(\w+)\/(\w+)$/, function(a, b){
console.log(a +' - '+ b);
});
awesomeRoute.rules = {
//keys match index of capturing groups
'0' : ['asd', 'qwe', 'zxc'];
'1' : function(val, request, values){
if(values[0] == 'asd' && val == '123'){
return false
} else {
return true;
}
}
};
crossroads.parse('asd/123'); //wont match
crossroads.parse('asd/456'); //match
Execute an action every time crossroads matches any route
Useful for tracking, debugging and any other action that may need to happen every time the app changes the current route.
//log all routes
crossroads.routed.add(console.log, console);
Execute an action every time crossroads can't match any route
Useful for tracking, debugging or handling errors.
//log all requests that were bypassed
crossroads.bypassed.add(console.log, console);
Execute an action when changing from current route to next one (v0.8.0+)
Useful for cases where you might need to trigger an action in between changes and that should only happen in a few cases.
var route1 = crossroads.addRoute('/foo/{bar}');
// will be triggered when current route is `route1` and crossroads is about to
// match another route
route1.switched.add(console.log, console);
Set Route priority
Routes with higher priority will be tested before. Routes are tested by order of creation if priority is omitted. The default priority is 0
.
crossroads.addRoute('/lorem');
crossroads.addRoute('/{foo}', null, 5);
crossroads.addRoute('/{bar}');
crossroads.parse('/lorem'); //will match second route since it has higher priority
Chaining
Some people loves chaining... if that's your thing, crossroads API can be chained till a certain extent, use it with care, code may become harder to read:
crossroads
.addRoute('{section}', loadSection)
.rules = {
section : ['home', 'login', 'contact']
};
Using with History.js
crossroads.js can be used with Hasher.js, but also with History.js. You'll need signals.js, crossroads.js, history.js, a history.js adapter (on their site). Example is in Coffeescript.
# ### Routes
# Route for some view that takes params
crossroads
.addRoute('{?query}', (query) ->
console.log(query)
)
# Route for an admin section
crossroads
.addRoute('/admin', ->
console.log("admin section")
)
# Process the landing route
crossroads.parse(document.location.pathname + document.location.search)
# log all requests that were bypassed / not matched
crossroads.bypassed.add(console.log, console);
# ### History management
History = window.History
if History.enabled
State = History.getState()
# set initial state to first page that was loaded
History.pushState
urlPath: window.location.pathname
, $("title").text(), State.urlPath
else
return false
loadAjaxContent = (target, urlBase, selector) ->
$(target).load urlBase + " " + selector
updateContent = (State) ->
selector = "#" + State.data.urlPath.substring(1)
if $(selector).length #content is already in #hidden_content
$("#content").children().appendTo "#hidden_content"
$(selector).appendTo "#content"
else
$("#content").children().clone().appendTo "#hidden_content"
loadAjaxContent "#content", State.url, selector
# Content update and back/forward button handler
History.Adapter.bind window, "statechange", ->
crossroads.parse(document.location.pathname + document.location.search)
# navigation link handler
$("body").on "click", "a", (e) ->
urlPath = $(this).attr("href")
if _(urlPath).startsWith('#') # probably some js function we don't want to mess with
return true
e.preventDefault()
title = $(this).text()
History.pushState
urlPath: urlPath
, title, urlPath
相关链接
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论