如何正确渲染部分视图,并使用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.xhrtesting

缺点

  • 我的模板中有逻辑。 不好

概要

这些都是我想到的和尝试过的技术,但是他们没有一个真正相信我。 难道我做错了什么 ? 有更清洁的技术吗? 或者我应该使用另一个模板引擎/框架?


问题#2

如果视图有自己的JavaScript文件,会发生什么(使用这些解决scheme)?

例如,使用解决scheme#4,如果我有两个页面, page_a.jadepage_b.jade ,它们都有自己的客户端JavaScript文件js / page_a.jsjs / 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.jsjs / 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。 另外,你要做到这一点时要灵活和理性。 可伸缩性也很重要。

常用的方法是:

  1. 预加载所有js文件可以在特定页面和AJAX请求处理程序中使用。 使用浏览器,如果你害怕潜在的名字冲突或脚本sideeffects。 asynchronous加载( 脚本asynchronous ),不让用户等待。

  2. 开发一个基本上可以在客户端执行的体系结构: loadPageAjax('page_a').then(...).catch(...) 。 关键是,既然你已经预先下载了所有的JS模块,在请求的调用者而不是在被调用 处运行你的AJAX页面相关的代码。

所以,你的解决scheme#4几乎是好的。 只需使用它来获取HTML,而不是脚本。

这种技术可以在不build立一些模糊的体系结构的情况下完成目标,而是使用单一的强大的目标限制方式。

很高兴回答任何进一步的问题,并说服你不要做你将要做的事情。

Interesting Posts