简介

一般网站的404页面包括两种类型:

1.路由匹配失败,没有此路由;

2.路由匹配成功,但参数不对,没有数据显示。

对于第一种情况,flow-routeriron-router都能轻松的处理,但对于第二种情况,iron-router可以设置dataNotFound的hook,而flow-router文档中并没有提及。本文就是对这种情况的总结。

场景还原

例如,在imports/startup/routes.js中,我定义了下面的路由,用于显示某个内容的详细信息:

1
2
3
4
5
6
FlowRouter.route('/detail/:queryId',{
name: 'detail',
action: function(){
BlazeLayout.render('mainLayout', {main: 'detail'});
}
});

其中参数:queryId对应于Mongo数据库中的_id字段,当_id不存在时,我需要显示404页面,而不再渲染此页面的detail模板。

其中BlazeLayout需要添加kadira:blaze-layout包:

1
2
3
4
#终端执行
meteor add kadira:blaze-layout
#在路由文件中引入
import { BlazeLayout } from 'meteor/kadira:blaze-layout';

具体用法请看文档

第一次尝试

为了检查:queryId是否存在,我想到了flow-router中的triggersEnter方法,当执行此路由时,先检查数据是否存在,如果不存在,就直接渲染notFound,并且停止路由中action方法的执行,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FlowRouter.route('/detail/:queryId',{
name: 'detail',
triggersEnter: [notExist],
action: function(){
BlazeLayout.render('mainLayout', {main: 'detail'});
}
});

#定义notExist函数
function notExist(context,redirect,stop){
let id = context.params.queryId;
console.log(id);
let detailContent = Resources.findOne({_id: id}) || {};
console.log(detailContent.fetch());
if (isEmpty(detailContent)) {
BlazeLayout.render('notFound');
stop();
}
}

其中notExist函数共有3个参数,context必须,redirect可以没有,stop此方法必须。
context中,我们可以得到其id,然后通过查询数据库,检查返回的数据是否为空,如果为空,就执行BlazeLayout.render('notFound');,并且停止路由的下一步进行。

其中isEmpty来源于lodash库:

1
2
3
4
#终端执行
meteor npm i -S lodash
#路由文件中引入
import { isEmpty } from 'lodash/lang';

分析结果

看起来逻辑没有错误,但是运行起来发现,当刷新页面时,就会显示404,而且浏览器终端也出现错误,通过console.log()对数据返回值的检查,发现Resources.findOne({_id: id})一直返回空对象,表示在route中数据库不起作用。

第二次尝试

在路由中没有成功,我准备在模板imports/ui/template/detail.js中再次尝试,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#在onCreated方法中订阅
Template.detail.onCreated(function() {
let self = this;
self.content = new ReactiveDict();
let queryId = FlowRouter.getParam('queryId');
self.subscribe('detail', queryId);
});
#在helpers中定义detail方法
Template.detail.helpers({
detail() {
let id = FlowRouter.getParam('queryId');
let detailContent = Resources.findOne({ _id: id }) || {};
if (isEmpty(detailContent)) {
FlowRouter.go('/404');
} else{
let instance = Template.instance();
instance.content.set('content-detail', detailContent);
console.log(detailContent,detailContent.contents.mimeType);
return detailContent;
}
}
})

通过FlowRouter.getParam得到id,然后数据库查找,如果数据库为空,显示404页面;如果不为空,储存返回值detailContentReactiveDict中,用于在模板中获取内容。

没想到,这次成功了,看来在路由文件声明中,不能使用数据库的查找。

小的改进

上面代码中,我使用了FlowRouter.go('/404'),这样就必须定义一个新的路由,没有必要,并且,浏览器地址栏就会变成如www.example.com/404;而如果使用BlazeLayout.render('notFound'),浏览器地址不会改变,如www.example.com/detail/ddddddd

可以直接改为:

1
2
3
if (isEmpty(detailContent)) {
BlazeLayout.render('notFound');
} else {...}

但是有一个问题,刷新页面时,会闪过mainLayout布局文件的内容,如navbar,需要继续改进:

1
2
3
if (isEmpty(detailContent)) {
BlazeLayout.render('mainLayout', {main: 'dataNotFound'});
} else {...}

这里,我新建了一个模板文件dataNotFound,因为notFound模板样式与mainLayout的样式不一定兼容,最好是分开。

这样,对于文章开头的两种情况:

  • notFound 对应于第一种,显示全局的404页面
  • dataNotFound 对应于第二种,显示数据没有找到的404页面

小的问题

Template.detail.helpers()中,不止一个helper,比如我还定义了isImage方法,用于检查类型是否为图片:

1
2
3
isImage(type) {
return type.indexOf('image/') > -1;
}

但是在浏览器终端,就会出现这样的错误信息:

error
error

原因出在哪,不清楚,但使用lodash中的startsWith方法替代后,错误信息就没了。

1
2
3
4
5
6
#首先引入
import { startsWith } from 'lodash/string';
#修改为
isImage(type) {
return startsWith(type, 'image/');
}

最后说明

Meteor版本: 1.4.1.1
Flow-router版本: 2.12.1

Meteor从1.3版本开始,就能使用npm包了,并且项目结构有所改变,比如在detail.js文件开头,使用ES6语法来引入可能需要的包:

1
2
3
4
5
6
7
8
9
10
11
12
#使用meteor add添加的包
import { Meteor } from 'meteor/meteor';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
import { ReactiveDict } from 'meteor/reactive-dict';
import { BlazeLayout } from 'meteor/kadira:blaze-layout';
#npm包
import { startsWith } from 'lodash/string';
import { isEmpty } from 'lodash/lang';
# 项目文件
import { Resources } from '../../api/resources.js';
import './detail.html';