问题
Say I wrote a blog app in Sails.js.
On every page in this app, there is a sidebar widget called "Recent Posts", where it lists the titles of the 5 most recent posts and clicking on them takes you to the post in question.
Because this sidebar widget is present on every page, it should be in layout.ejs
. But, here we have a conflict - dynamic content is only supposed to be pulled from the database in the controller action for rendering a specific view.
This dynamic content isn't for a specific view, it's for the whole site (via layout.ejs).
By the conventions that I understand, I'd have to get that dynamic content data for the sidebar widget in every controller action that renders a view (otherwise I would get an undefined error when I attempt to call that local in my layout.ejs file).
Things I've tried / considered:
Load that dynamic content in every controller action that renders a view (this solution is very bad) and calling that dynamic content in layout.ejs as if it were a local for the specific view. This works fine, but goes against D.R.Y. principles and quite frankly is a pain in the ass to have to run the same query to the database in every controller action.
As per another similar stackoverflow question, create a new config (E.G.
config/globals.js
), load my dynamic content from my database into that config file as a variable, and then callingsails.config.globals.[variable_name]
in my layout.ejs file. This also worked, since apparently config variables are available everywhere in the application -- but it 's a hacky solution that I'm not a fan of (the content I'm loading is simply the titles and slugs of 5 recent posts, not a "global config option", as the solution implies).Run the query to get the dynamic content inside the .EJS file directly between some
<% %>
tags. I'm not sure if this would work, but even if it did, it goes against the separation of concerns MVC principle and I'd like to avoid doing this if at all possible (if it even works).As per a lengthy IRC discussion @ http://webchat.freenode.net/?channels=sailsjs, it was suggested to create a policy and map that policy to all my controllers. In that policy, query the database for the 5 most recent posts, and set them to the req.recentposts. The problem with this solution is that, while the recent posts data will be passed to every controller, I still have to pass that req.recentposts data to my view -- making it so I still have to modify every single res.view({}) in every action. I don't have to have the database query in every action, which is good, but I still have to add a line of code to every action that renders a view... this isn't D.R.Y. and I'm looking for a better solution.
So, what is the proper solution, without needing to load that dynamic content in every controller action (a solution that adheres to D.R.Y. is what I'm lookng for), to get some dynamic content available to my layout.ejs file?
回答1:
In folder /config you should create a file express.js and add something like that:
module.exports.express = {
customMiddleware: function(app){
app.use(function(req, res, next){
// or whatever query you need
Posts.find().limit(5).exec(function(err, posts){
res.locals.recentPosts = posts;
// remember about next()
next();
});
});
}
}
Then just make some simple loop in your view:
<% for(var i=0; i<recentPosts.length; i++) { %>
<% recentPosts[i].title %>
<% } %>
Here are some links to proper places in documentation: https://github.com/balderdashy/sails-docs/blob/0.9/reference/Configuration.md#express and https://github.com/balderdashy/sails-docs/blob/0.9/reference/Response.md#reslocals
回答2:
I found out another way to do this. What I did was to create a service that could render .ejs files to plain html by simply taking advantage of the ejs library already in sails. This service could either be invoked by the controller, or even passed as a function in the locals, and executed from within the .ejs. The service called TopNavBarService would look like:
var ejs = require('ejs');
exports.render = function() {
/* database finds goes here */
var userInfo = {
'username' : 'Kallehopp',
'real_name' : 'Kalle Hoppson'
};
var html = null;
ejs.renderFile('./views/topNavBar.ejs', {'locals':userInfo}, function(err, result) { html = result; });
return html;
}
In the constroller it could look like:
module.exports = {
testAction: function (req, res) {
return res.view('testView', {
renderNavbar: TopNavBarService.render // service function as a local!
});
}
};
This way you can create your customized ejs-helper that could even take arguments (although not shown here). When invoked, the helper could access the database and render a part of the html.
<div>
<%- renderNavbar() %>
</div>
来源:https://stackoverflow.com/questions/20267550/what-is-the-proper-way-to-integrate-dynamic-content-into-the-layout-ejs-file-in