如何正确渲染部分视图,并使用 Express/Jade 在 AJAX 中加载 JavaScript 文件?
Posted
技术标签:
【中文标题】如何正确渲染部分视图,并使用 Express/Jade 在 AJAX 中加载 JavaScript 文件?【英文标题】:How to properly render partial views, and load JavaScript files in AJAX using Express/Jade? 【发布时间】:2014-09-27 20:16:31 【问题描述】:总结
我正在为我的 Web 应用程序使用 Express + Jade,但我正在为我的 AJAX 导航渲染部分视图而苦苦挣扎。
我有两个不同的问题,但它们是完全相关的,所以我将它们包含在同一个帖子中。 我想这将是一篇很长的文章,但我保证如果你已经在同样的问题上苦苦挣扎,那它会很有趣。如果有人花时间阅读并提出解决方案,我将不胜感激。
TL;DR: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");
);
优势:
现在我的内容只定义一次,这样更好。缺点:
对于一个页面,我仍然需要两个文件。 我必须在 Express 中的每条路线上使用相同的技术。一世 刚刚将我的代码重复问题从 Jade 移至 Express。快拍。解决方案 3:与解决方案 2 相同,但修复了代码重复问题。
使用Alex Ford's technique,我可以在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
我不喜欢为 one 页面使用 two 文件,那么如果我让 Jade 决定要渲染什么而不是 Express 呢?乍一看,这对我来说似乎很不舒服,因为我认为模板引擎根本不应该不处理任何逻辑。不过让我们试试吧。
首先,我需要将一个变量传递给 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
会发生什么?我的页面将是以下的编译版本:
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 文件应该在我的block content
中,这样当我转到另一个页面时它们会被删除。因此,我的 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
对吗? Err....如果我去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 文件?服务器端使用不同的策略/模板引擎?客户端?
如果你做到了这一步,你就是一个真正的英雄,我很感激,但如果你能给我一些建议,我会更感激:)
【问题讨论】:
你知道如何在部分渲染后加载 javascript 文件吗?我现在遇到了类似的问题。 我希望我可以 +10 这个问题 我希望我能更多地支持这个。目前我在添加部分后尝试加载 css 和 js 文件。我的解决方法很糟糕,但我使用 res.render 回调将翡翠模板转换为字符串,然后发送 html 和对对象中其他文件的引用,然后我使用 javascript 处理这些文件。我确信这是一种可怕的方法,但是我不喜欢我的身体中的链接标签 您好,谢谢您的回复。我很久以前在 Jade 的 git repo 上 reported a bug (事实上,这不是一个真正的错误)。我遇到的问题与此无关,但请查看关于该主题的最后两篇文章。 Forbes Lindesay 就我们的问题提供了建议。 【参考方案1】:很好的问题。我没有完美的选择,但我会提供我喜欢的解决方案#3 的变体。与解决方案 #3 相同的想法,但将 _full 文件的翡翠模板移动到您的代码中,因为它是样板文件,并且 javascript 可以在需要完整页面时生成它。免责声明:未经测试,但我谦虚地建议:
app.use(function (req, res, next)
var template = "extends layout.jade\n\nblock content\n include ";
res.renderView = function (viewName, opts)
if (req.xhr)
var renderResult = jade.compile(template + viewName + ".jade\n", opts);
res.send(renderResult);
else
res.render(viewName, opts);
next();
;
);
随着您的场景变得更加复杂,您可以更加巧妙地利用这个想法,例如将此模板保存到一个文件名占位符的文件中。
当然,这仍然不是一个完美的解决方案。您正在实现真正应该由模板引擎处理的功能,这与您最初对解决方案 #3 的反对意见相同。如果您最终为此编写了超过几十行代码,那么请尝试找到一种方法将该功能融入 Jade 并向他们发送拉取请求。例如,如果翡翠“扩展”关键字采用了一个可以禁用扩展 xhr 请求布局的参数......
对于您的第二个问题,我不确定是否有任何模板引擎可以帮助您。如果您使用 ajax 导航,当导航通过一些后端模板魔术发生时,您不能很好地“卸载”page_a.js。我认为您必须为此(客户端)使用传统的javascript隔离技术。针对您的具体问题:首先在闭包中实现页面特定逻辑(和变量),并在必要时在它们之间进行合理的合作。其次,您无需过多担心连接双事件处理程序。假设主要内容在 ajax 导航中被清除,并且那些附加了事件处理程序的元素会在新的 ajax 内容加载到 DOM 时调用 get reset(当然)。
【讨论】:
这是一个有趣的答案@Segfault,谢谢。我为我的应用程序使用了这个解决方案#4。关于第二个问题,好的建议,虽然我不得不在事件部分不同意你的看法。事件可以绑定到文档(事件委托...)或窗口(@987654322@,这是一个糟糕的例子,但你明白了)。 是的,好点@WaldoJeffers 我想你仍然需要担心这些事件。我认为这对于一般情况很难解决......【参考方案2】:不确定它是否真的能帮助你,但我用 Express 和 ejs 模板解决了同样的问题。
我的文件夹:
app.js views/header.ejs views/page.ejs views/footer.ejs我的 app.js
app.get('/page', function(req, res)
if (req.xhr)
res.render('page',ajax:1);
else
res.render('page',ajax:0);
);
我的页面.ejs
<% if (ajax == 0) %> <% include header %> <% %>
content
<% if (ajax == 0) %> <% include footer %><% %>
非常简单,但效果很好。
【讨论】:
【参考方案3】:有几种方法可以按照您的要求进行操作,但所有方法在架构上都很糟糕。你现在可能没有注意到,但随着时间的推移,情况会越来越糟。
此时听起来很奇怪,但您真的不想通过 AJAX 向您传输 JS、CSS。
您想要的是能够通过 AJAX 获取标记并在此之后执行一些 Javascript。此外,您希望在执行此操作时保持灵活和理性。可扩展性也很重要。
常见的方式是:
预加载所有js
文件可用于特定页面及其AJAX 请求处理程序。如果您担心潜在的名称冲突或脚本副作用,请使用 browserify。异步加载它们 (script async) 以免用户等待。
开发一种架构,使您基本上可以在客户端执行此操作:loadPageAjax('page_a').then(...).catch(...)
。关键是,由于您已经预先下载了所有 JS 模块,因此要在请求的 caller 而不是 callee 处运行您的 AJAX 页面相关代码。
所以,您的解决方案#4 几乎是好的。只需使用它来获取 HTML,而不是脚本。
这种技术可以在不构建一些晦涩的架构的情况下完成您的目标,而是使用单一健壮的目标受限方式。
很乐意回答任何进一步的问题,并说服你不要做你想做的事。
【讨论】:
以上是关于如何正确渲染部分视图,并使用 Express/Jade 在 AJAX 中加载 JavaScript 文件?的主要内容,如果未能解决你的问题,请参考以下文章