摘要
我正在使用Express + Jade制作我的Web应用程序,并且在呈现AJAX导航的部分视图方面遇到了一些困难。
我有两个不同的问题,但它们是完全相关的,因此我将它们放在同一个帖子中。我想这将是一个长帖子,但如果您已经遇到过相同的问题,我保证它很有趣。如果有人愿意花时间阅读并提出解决方案,我将不胜感激。
简明扼要:2个问题
- 使用Express + Jade呈现AJAX导航的视图片段的最干净和最快速的方法是什么?
- 应如何加载与每个视图相关的JavaScript文件?
要求
- 我的Web应用程序需要与禁用JavaScript的用户兼容
- 如果启用JavaScript,则只应从服务器向客户端发送页面自己的内容(而不是整个布局)
- 应用程序需要快速,尽可能少地加载字节
问题#1:我尝试的方法
解决方案1:为AJAX和非AJAX请求使用不同的文件
我的layout.jade 是:
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
我的page_full.jade文件是:
extends layout.jade
block content
h1 Hey Welcome !
我的page_ajax是:
h1 Hey Welcome
最后,在 router.js 文件中(Express):
app.get("/page",function(req,res,next){
if (req.xhr) res.render("page_ajax.jade");
else res.render("page_full.jade");
});
缺点:
- 正如你可能猜到的那样,每次需要更改内容时,我都必须两次编辑我的视图。相当令人沮丧。
解决方案2:使用`include`相同的技术
我的layout.jade保持不变:
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
我的page_full.jade文件现在是:
extends layout.jade
block content
include page.jade
我的page.jade文件只包含实际内容,没有任何布局、块、扩展或包含:
h1 Hey Welcome
我现在在 router.js (Express) 中有以下内容:
app.get("/page",function(req,res,next){
if (req.xhr) res.render("page.jade");
else res.render("page_full.jade");
});
优点:
- 现在我的内容只需定义一次,这更好。
缺点:
- 我仍然需要两个文件才能呈现一个页面。
- 我必须在每个路由上使用相同的技术,我只是将我的代码重复问题从Jade转移到了Express。Snap。
解决方案3:与解决方案2相同,但解决了代码重复问题。
使用Alex Ford的技术,我可以在middleware.js中定义自己的render
函数:
app.use(function (req, res, next) {
res.renderView = function (viewName, opts) {
res.render(viewName + req.xhr ? null : '_full', opts);
next();
};
});
然后将 router.js (Express) 改为:
app.get("/page",function(req,res,next){
res.renderView("/page");
});
保持其他文件不变。
优点
- 解决了代码重复的问题。
缺点
- 我仍然需要使用两个文件来渲染一个页面。
- 定义自己的
renderView
方法感觉有点不好。毕竟,我希望我的模板引擎/框架为我处理这个问题。
解决方案4:将逻辑移到Jade中
我不喜欢在一个页面中使用两个文件,那么如果让Jade决定要渲染什么呢?乍一看,这对我来说非常不舒服,因为我认为模板引擎应该不处理任何逻辑。但是让我们尝试一下。
首先,我需要传递一个变量给Jade,告诉它这是什么样的请求:
在middleware.js中(Express)
app.use(function (req, res, next) {
res.locals.xhr = req.xhr;
});
现在我的 layout.jade 文件与之前的相同:
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
我的page.jade文件将是:
if (!locals.xhr)
extends layout.jade
block content
h1 Hey Welcome !
厉害了?但这样行不通,因为在Jade中无法使用条件继承。所以我可以把测试从page.jade移到layout.jade:
if (!locals.xhr)
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
else
block content
然后 page.jade 将返回:
extends layout.jade
block content
h1 Hey Welcome !
优点:
- 现在每个页面只有一个文件
- 我不需要在每个路由或视图中重复
req.xhr
测试
缺点:
- 我的模板中有逻辑。不好
摘要
这些都是我考虑并尝试过的技术,但没有一个真正让我信服。我做错了什么吗?有更干净的技术吗?或者我应该使用其他的模板引擎/框架?
问题#2
如果视图有自己的JavaScript文件会发生什么(使用任何这些解决方案)?
例如,使用解决方案#4,如果我有两个页面,page_a.jade和page_b.jade,它们都有自己的客户端JavaScript文件js/page_a.js和js/page_b.js,当这些页面通过AJAX加载时会发生什么?
首先,我需要在layout.jade中定义一个extraJS
块:
if (!locals.xhr)
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
// Shared JS files go here
script(src="js/jquery.min.js")
// Specific JS files go there
block extraJS
else
block content
// Specific JS files go there
block extraJS
然后 page_a.jade 将会是:
extends layout.jade
block content
h1 Hey Welcome !
block extraJS
script(src="js/page_a.js")
如果我在我的URL栏中输入localhost/page_a
(非AJAX请求),我将得到以下编译版本:doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome A !
script(src="js/jquery.min.js")
script(src="js/page_a.js")
那看起来不错。但是如果我现在使用我的 AJAX 导航到 page_b 会发生什么?我的页面将是以下编译版本:
那看起来不错。但是如果我现在使用我的 AJAX 导航 到 page_b
会发生什么?我的页面将是以下的编译版本:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome B !
script(src="js/page_b.js")
script(src="js/jquery.min.js")
script(src="js/page_a.js")
js/page_a.js和js/page_b.js都在同一个页面上加载。如果存在冲突(相同的变量名等),会发生什么?此外,如果我使用AJAX返回到localhost/page_a,则会出现以下情况:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome B !
script(src="js/page_a.js")
script(src="js/jquery.min.js")
script(src="js/page_a.js")
同一页面上加载相同的JavaScript文件(page_a.js)两次!这会导致冲突,每个事件会触发两次吗?
无论如何,我认为这不是干净的代码。因此,您可以说特定的JS文件应该在我的“块内容”中,以便在转到另一页时将其删除。 因此,我的layout.jade应该是:
if (!locals.xhr)
doctype html
html(lang="fr")
head
// Shared CSS files go here
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
block content
block extraJS
// Shared JS files go here
script(src="js/jquery.min.js")
else
block content
// Specific JS files go there
block extraJS
对吗?嗯...如果我进入localhost/page_a
,我将得到一个已经编译好的版本:
doctype html
html(lang="fr")
head
link(type="text/css",rel="stylesheet",href="css/bootstrap.min.css")
body
div#main_content
h1 Hey Welcome A !
script(src="js/page_a.js")
script(src="js/jquery.min.js")
你可能已经注意到了,js/page_a.js实际上是在jQuery之前被加载的,因此它不起作用,因为jQuery尚未定义...所以我不知道如何解决这个问题。我想通过客户端处理脚本请求来解决这个问题,例如使用jQuery.getScript()
,但客户端必须知道脚本的文件名,查看它们是否已经加载,可能会删除它们。我认为不应该在客户端完成。
我应该如何处理通过AJAX加载的JavaScript文件?是使用不同的服务器端策略/模板引擎?还是客户端处理?
如果你已经阅读到这里,你就是一个真正的英雄,我很感激你,但如果你能给我一些建议,我会更加感激 :)