{"action":"created","issue":{"url":"https://api.github.com/repos/RandomEtc/ejs-locals/issues/24","repository_url":"https://api.github.com/repos/RandomEtc/ejs-locals","labels_url":"https://api.github.com/repos/RandomEtc/ejs-locals/issues/24/labels{/name}","comments_url":"https://api.github.com/repos/RandomEtc/ejs-locals/issues/24/comments","events_url":"https://api.github.com/repos/RandomEtc/ejs-locals/issues/24/events","html_url":"https://github.com/RandomEtc/ejs-locals/issues/24","id":9881948,"number":24,"title":"Support for theme folder","user":{"login":"olivercox","id":3074104,"avatar_url":"https://avatars.githubusercontent.com/u/3074104?v=3","gravatar_id":"","url":"https://api.github.com/users/olivercox","html_url":"https://github.com/olivercox","followers_url":"https://api.github.com/users/olivercox/followers","following_url":"https://api.github.com/users/olivercox/following{/other_user}","gists_url":"https://api.github.com/users/olivercox/gists{/gist_id}","starred_url":"https://api.github.com/users/olivercox/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/olivercox/subscriptions","organizations_url":"https://api.github.com/users/olivercox/orgs","repos_url":"https://api.github.com/users/olivercox/repos","events_url":"https://api.github.com/users/olivercox/events{/privacy}","received_events_url":"https://api.github.com/users/olivercox/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2013-01-11T14:47:25Z","updated_at":"2016-07-24T04:16:52Z","closed_at":null,"body":"I've been trying to find a way of handling template overrides with a theme folder. The project I'm working on will implement themes that can override a default template. I would like to keep the syntax of calling templates so the following statement would look for the file in a theme folder first and load it if exists.\r\n\r\n<code><% layout('layouts/public') -%>\r\n</code>\r\n\r\nSo lets say that the default views folder is 'root/views' and the theme folder is 'root/themes/mytheme'. The engine would try to find 'layouts/public.ejs' relative to the theme folder and the file the template was called from. If it doesn't exist it should attempt to load the from the views folder. \r\n\r\nIf the template was called from \r\n<code>root/views/mymodule/index.ejs</code>\r\nit would look first for the file \r\n<code>root/themes/mytheme/mymodule/layouts/public.ejs</code> \r\nif not found it would look for \r\n<code>root/views/layouts/public.ejs</code>\r\n\r\nI've found a possible way to handle this by editing the renderFile method in index.js. I'd appreciate any feedback on the change and how one would go about getting it included.\r\n\r\nI've added the following lines just before the call to ejs.renderFile()\r\n\r\n<pre><code>\r\n//Get the theme folder (options.settings['theme'])\r\n\tvar themeFilePath = dirname(file).replace(resolve(options.settings.views), resolve(options.settings['theme']))\r\n\t//If the current file exists reletive to the theme folder set the file to that path\r\n\tif (fs.existsSync(resolve(themeFilePath, basename(file)))) file = resolve(themeFilePath, basename(file));`\r\n</code></pre>\r\nThis relies on the following being set when the view engine is declared, in my case in the server.js:\r\n\r\n<pre><code>\r\n app.set('theme', __dirname + [PATH_TO_THEME]);\r\n</code></pre>\r\n\r\nThe ejs-locals renderFile method now looks like:\r\n\r\n<pre><code>\r\nvar renderFile = module.exports = function (file, options, fn) {\r\n\r\n\t// Express used to set options.locals for us, but now we do it ourselves\r\n\t// (EJS does some __proto__ magic to expose these funcs/values in the template)\r\n\tif (!options.locals) {\r\n\t\toptions.locals = {};\r\n\t}\r\n\r\n\tif (!options.locals.blocks) {\r\n\t\t// one set of blocks no matter how often we recurse\r\n\t\tvar blocks = { scripts: new Block(), stylesheets: new Block() };\r\n\t\toptions.locals.blocks = blocks;\r\n\t\toptions.locals.scripts = blocks.scripts;\r\n\t\toptions.locals.stylesheets = blocks.stylesheets;\r\n\t\toptions.locals.block = block.bind(blocks);\r\n\t\toptions.locals.stylesheet = stylesheet.bind(blocks.stylesheets);\r\n\t\toptions.locals.script = script.bind(blocks.scripts);\r\n\t}\r\n\t// override locals for layout/partial bound to current options\r\n\toptions.locals.layout = layout.bind(options);\r\n\toptions.locals.partial = partial.bind(options);\r\n\r\n\t//Get the theme folder (options.settings['theme'])\r\n\tvar themeFilePath = dirname(file).replace(resolve(options.settings.views), resolve(options.settings['theme']))\r\n\t//If the current file exists reletive to the theme folder set the file to that path\r\n\tif (fs.existsSync(resolve(themeFilePath, basename(file)))) file = resolve(themeFilePath, basename(file));\r\n\r\n\tejs.renderFile(file, options, function (err, html) {\r\n\r\n\t\tif (err) {\r\n\t\t\treturn fn(err, html);\r\n\t\t}\r\n\r\n\t\tvar layout = options.locals._layoutFile;\r\n\r\n\t\t// for backward-compatibility, allow options to\r\n\t\t// set a default layout file for the view or the app\r\n\t\t// (NB:- not called `layout` any more so it doesn't\r\n\t\t// conflict with the layout() function)\r\n\t\tif (layout === undefined) {\r\n\t\t\tlayout = options._layoutFile;\r\n\t\t}\r\n\r\n\t\tif (layout) {\r\n\r\n\t\t\t// use default extension\r\n\t\t\tvar engine = options.settings['view engine'] || 'ejs',\r\n          desiredExt = '.' + engine;\r\n\r\n\t\t\t// apply default layout if only \"true\" was set\r\n\t\t\tif (layout === true) {\r\n\t\t\t\tlayout = path.sep + 'layout' + desiredExt;\r\n\t\t\t}\r\n\t\t\tif (extname(layout) !== desiredExt) {\r\n\t\t\t\tlayout += desiredExt;\r\n\t\t\t}\r\n\r\n\t\t\t// clear to make sure we don't recurse forever (layouts can be nested)\r\n\t\t\tdelete options.locals._layoutFile;\r\n\t\t\tdelete options._layoutFile;\r\n\t\t\t// make sure caching works inside ejs.renderFile/render\r\n\t\t\tdelete options.filename;\r\n\r\n\t\t\tif (layout.length > 0 && layout[0] === path.sep) {\r\n\t\t\t\t// if layout is an absolute path, find it relative to view options:\r\n\t\t\t\tlayout = join(options.settings.views, layout.slice(1));\r\n\t\t\t} else {\r\n\t\t\t\t// otherwise, find layout path relative to current template:\r\n\t\t\t\tlayout = resolve(dirname(file), layout);\r\n\t\t\t}\r\n\r\n\t\t\t// now recurse and use the current result as `body` in the layout:\r\n\t\t\toptions.locals.body = html;\r\n\t\t\trenderFile(layout, options, fn);\r\n\t\t} else {\r\n\t\t\t// no layout, just do the default:\r\n\t\t\tfn(null, html);\r\n\t\t}\r\n\t});\r\n\r\n};\r\n</code></pre>"},"comment":{"url":"https://api.github.com/repos/RandomEtc/ejs-locals/issues/comments/234756517","html_url":"https://github.com/RandomEtc/ejs-locals/issues/24#issuecomment-234756517","issue_url":"https://api.github.com/repos/RandomEtc/ejs-locals/issues/24","id":234756517,"user":{"login":"gsalgadotoledo","id":1288502,"avatar_url":"https://avatars.githubusercontent.com/u/1288502?v=3","gravatar_id":"","url":"https://api.github.com/users/gsalgadotoledo","html_url":"https://github.com/gsalgadotoledo","followers_url":"https://api.github.com/users/gsalgadotoledo/followers","following_url":"https://api.github.com/users/gsalgadotoledo/following{/other_user}","gists_url":"https://api.github.com/users/gsalgadotoledo/gists{/gist_id}","starred_url":"https://api.github.com/users/gsalgadotoledo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gsalgadotoledo/subscriptions","organizations_url":"https://api.github.com/users/gsalgadotoledo/orgs","repos_url":"https://api.github.com/users/gsalgadotoledo/repos","events_url":"https://api.github.com/users/gsalgadotoledo/events{/privacy}","received_events_url":"https://api.github.com/users/gsalgadotoledo/received_events","type":"User","site_admin":false},"created_at":"2016-07-24T04:16:52Z","updated_at":"2016-07-24T04:16:52Z","body":"Hi @olivercox It looks like the same problem that I have\r\n"}}
{"actor":{"display_login":"gsalgadotoledo"}}