摘要
我正在使用Express + Jade作为我的网络应用程序,我正在努力为我的AJAX导航渲染部分视图。
我有两个不同的问题,但它们是完全相关的,所以我将它们包括在同一篇文章中。 我想这将是一个很长的帖子,但如果你已经在努力解决同样的问题,我保证这很有意思。如果有人花时间阅读和阅读,我会非常感激。提出解决方案。
TL; DR:2个问题
要求
我的 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 (快递):
app.get("/page",function(req,res,next){
if (req.xhr) res.render("page_ajax.jade");
else res.render("page_full.jade");
});
缺点:
我的 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 (快递):
app.get("/page",function(req,res,next){
if (req.xhr) res.render("page.jade");
else res.render("page_full.jade");
});
优势:
缺点:
使用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
方法感觉很肮脏。毕竟,我
希望我的模板引擎/框架能够为我处理这个问题。我不想为一个页面使用两个文件,那么如果我让Jade决定渲染什么而不是Express呢?乍一看,对我来说似乎很不舒服,因为我认为模板引擎应该不处理任何逻辑。但是,试试吧。
首先,我需要将一个变量传递给Jade,它将告诉它它是什么类型的请求:
在 middleware.js (快速)
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
测试
图缺点:
这些都是我想到并试过的技巧,但没有一个真正让我信服。难道我做错了什么 ?有清洁技术吗?或者我应该使用其他模板引擎/框架吗?
如果视图有自己的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")
如果我在网址栏中输入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
对吗?呃......如果我去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文件?服务器端使用不同的策略/模板引擎?客户端?
如果你已经做到这一点,你就是一个真正的英雄,我很感激,但如果你能给我一些建议我会更感激:)
答案 0 :(得分:3)
好问题。我没有完美的选择,但我会提供我喜欢的解决方案#3的变体。与解决方案#3相同的想法,但将_full文件的jade模板移动到您的代码中,因为它是样板文件,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(当然)。
答案 1 :(得分:1)
不确定它是否真的会帮助你,但我用Express和ejs模板解决了同样的问题。
我的文件夹:
我的app.js
app.get('/page', function(req, res) {
if (req.xhr) {
res.render('page',{ajax:1});
} else {
res.render('page',{ajax:0});
}
});
my page.ejs
<% if (ajax == 0) { %> <% include header %> <% } %>
content
<% if (ajax == 0) { %> <% include footer %><% } %>
非常简单,但效果很好。
答案 2 :(得分:0)
有几种方法可以做你所要求的,但所有这些方法在建筑上都很糟糕。你现在可能没有注意到,但随着时间的推移会越来越糟。
此时听起来很奇怪,但你真的不希望通过AJAX向你发送JS,CSS。
你想要的是能够通过AJAX获得标记并在那之后做一些Javascript。此外,您希望在执行此操作时保持灵活和理性。可扩展性也很重要。
常见的方法是:
预加载所有js
文件可以在特定页面及其AJAX请求处理程序中使用。如果您害怕潜在的名称冲突或脚本副作用,请使用browserify。异步加载它们(script async)以避免用户等待。
开发一个架构,使您能够在客户端基本上执行此操作:loadPageAjax('page_a').then(...).catch(...)
。关键是,由于您已预先下载了所有JS模块,因此要在请求的调用者处运行与AJAX页面相关的代码,而不是被调用者。< / p>
所以,你的解决方案#4 几乎是好的。只需使用它来获取HTML,而不是脚本。
这种技术可以在不构建一些模糊的架构的情况下完成您的目标,但是使用单一的强大目标限制方式。
很乐意回答任何进一步的问题并说服你不要做你想做的事。