如何正确渲染部分视图,并使用Express / Jade在AJAX中加载JavaScript文件?
概要
我正在为我的Web应用程序使用Express + Jade,并且正在努力为我的AJAX导航渲染部分视图。
我有两个不同的问题,但他们是完全相关的,所以我把他们包括在同一个职位。 我想这将是一个很长的post,但我保证这是有趣的,如果你已经在同样的问题挣扎。 如果有人花时间阅读和提出解决scheme,我将非常感激。
TL; DR:2个问题
- 使用Express + Jade为AJAX导航呈现视图片段的最干净 , 最快捷的方式是什么?
- 应该如何加载相对于每个视图的JavaScript文件?
要求
- 我的Web应用程序需要与已禁用的用户兼容
JavaScript的 - 如果启用了JavaScript,则只有页面自己的内容(而不是整个布局)应该从服务器发送到客户端
- 该应用程序需要快速,并加载尽可能less的字节
问题1:我试过了
解决scheme1:针对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"); });
缺点 :
- 正如你可能猜到的,每次我需要改变一些东西的时候,我都要编辑我的观点两次。 非常沮丧。
解决scheme2:与`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 (快车):
app.get("/page",function(req,res,next){ if (req.xhr) res.render("page.jade"); else res.render("page_full.jade"); });
优点 :
- 现在我的内容只定义一次,这样更好。
缺点 :
- 我仍然需要一个页面的两个文件。
- 在每条路线上,我必须在快车上使用相同的技巧。 我只是把我的代码重复问题从Jade移到Express。 捕捉。
解决scheme3:与解决scheme2相同,但解决了代码重复问题。
使用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
方法感觉很肮脏。 毕竟,我希望我的模板引擎/框架能够为我处理这个问题。
解决scheme4:将逻辑移至Jade
我不喜欢在一个页面上使用两个文件,那么如果我让Jade决定渲染什么而不是Express呢? 乍看之下,我觉得非常不舒服,因为我认为模板引擎根本不应该处理任何逻辑。 但是,让我们试试。
首先,我需要传递一个variables给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 !
很棒吧? 翡翠除了不可能有条件的延伸是不行的。 所以我可以将testing从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
testing
缺点 :
- 我的模板中有逻辑。 不好
概要
这些都是我想到的和尝试过的技术,但是他们没有一个真正相信我。 难道我做错了什么 ? 有更清洁的技术吗? 或者我应该使用另一个模板引擎/框架?
问题#2
如果视图有自己的JavaScript文件,会发生什么(使用这些解决scheme)?
例如,使用解决scheme#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栏中inputlocalhost/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都加载在同一页面上。 如果发生冲突(相同的variables名等)会发生什么? 另外,如果我回到本地主机/ 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 B ! script(src="js/page_a.js") script(src="js/jquery.min.js") script(src="js/page_a.js")
相同的JavaScript文件( page_a.js )在同一页面上加载两次! 它会引起冲突,每个事件双重解雇? 不pipe是不是这种情况,我不认为这是干净的代码。
所以你可能会说,具体的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文件? 服务器端使用不同的策略/模板引擎? 客户端 ?
如果你已经做到了这一点,你是一个真正的英雄,我很感激,但我会更感激,如果你能给我一些build议:)
伟大的问题。 我没有一个完美的select,但我会提供一个我喜欢的解决scheme#3的变体。 与解决scheme#3相同的想法,但将_full文件的玉石模板移动到您的代码,因为它是样板,JavaScript可以生成一个完整的页面需要时。 免责声明:未经testing,但我虚心build议:
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(); }; });
随着情景变得更加复杂,您可以更加聪明地使用这个想法,例如使用文件名的占位符将此模板保存到文件。
当然这还不是一个完美的解决scheme。 您正在实现应该真正由模板引擎处理的function,与您对解决scheme#3的原始异议相同。 如果你最终编写了几十行代码,那么试着find一种方法将这个特性适合Jade并发送给他们一个pull请求。 例如,如果玉“扩展”关键字采取了一个参数,可以禁用扩展布局的XHR请求…
对于你的第二个问题,我不确定任何模板引擎可以帮助你。 如果你使用ajax导航,当导航通过一些后端模板魔法发生时,你不能很好地“卸载”page_a.js。 我认为你必须使用传统的JavaScript隔离技术(客户端)。 针对您的具体问题:在闭包中实现页面特定的逻辑(和variables),作为初学者,并在必要时进行合理的合作。 其次,你不必担心连接双重事件处理程序。 假设主要内容在ajax导航上被清除,并且那些事件处理程序被连接的元素会在新的ajax内容加载到DOM时调用get reset(当然)。
不知道它是否真的会帮助你,但我解决了与Express和ejs模板相同的问题。
我的文件夹:
- app.js
- 意见/ header.ejs
- 意见/ page.ejs
- 意见/ footer.ejs
我的app.js
app.get('/page', function(req, res) { if (req.xhr) { res.render('page',{ajax:1}); } else { res.render('page',{ajax:0}); } });
我的page.ejs
<% if (ajax == 0) { %> <% include header %> <% } %> content <% if (ajax == 0) { %> <% include footer %><% } %>
很简单,但作品完美。
有几种方法可以完成你所要求的,但是所有这些方法在结构上都是不好的。 现在你可能没有注意到,但随着时间的推移,情况会越来越糟糕。
听起来很奇怪,但是你并不想通过AJAX传输给你JS,CSS。
你想要的是能够通过AJAX获得标记,然后做一些Javascript。 另外,你要做到这一点时要灵活和理性。 可伸缩性也很重要。
常用的方法是:
-
预加载所有
js
文件可以在特定页面和AJAX请求处理程序中使用。 使用浏览器,如果你害怕潜在的名字冲突或脚本sideeffects。 asynchronous加载( 脚本asynchronous ),不让用户等待。 -
开发一个基本上可以在客户端执行的体系结构:
loadPageAjax('page_a').then(...).catch(...)
。 关键是,既然你已经预先下载了所有的JS模块,在请求的调用者而不是在被调用 者处运行你的AJAX页面相关的代码。
所以,你的解决scheme#4几乎是好的。 只需使用它来获取HTML,而不是脚本。
这种技术可以在不build立一些模糊的体系结构的情况下完成目标,而是使用单一的强大的目标限制方式。
很高兴回答任何进一步的问题,并说服你不要做你将要做的事情。